diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000000..b21223c3726 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Two years until issues go stale +daysUntilStale: 730 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/README.md b/README.md index 9a7ce3ff074..bf7df70bf1c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ HAPI FHIR - Java API for HL7 FHIR Clients and Servers [![Build Status](https://dev.azure.com/jamesagnew214/jamesagnew214/_apis/build/status/jamesagnew.hapi-fhir?branchName=master)](https://dev.azure.com/jamesagnew214/jamesagnew214/_build/latest?definitionId=1&branchName=master) [![codecov](https://codecov.io/gh/jamesagnew/hapi-fhir/branch/master/graph/badge.svg)](https://codecov.io/gh/jamesagnew/hapi-fhir) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.uhn.hapi.fhir/hapi-fhir-base/badge.svg)](http://search.maven.org/#search|ga|1|ca.uhn.hapi.fhir) -[![License](https://img.shields.io/badge/license-apache%202.0-60C060.svg)](http://jamesagnew.github.io/hapi-fhir/license.html) +[![License](https://img.shields.io/badge/license-apache%202.0-60C060.svg)](https://hapifhir.io/hapi-fhir/license.html) Complete project documentation is available here: http://hapifhir.io diff --git a/example-projects/hapi-fhir-jpaserver-cds-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/example-projects/hapi-fhir-jpaserver-cds-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html index dfc4769d6e4..fa0fa5666f6 100644 --- a/example-projects/hapi-fhir-jpaserver-cds-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ b/example-projects/hapi-fhir-jpaserver-cds-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html @@ -40,10 +40,8 @@

- - - This is not a production server! - + + This is not a production server! Do not store any information here that contains personal health information or any other confidential information. This server will be regularly purged and reloaded with fixed test data. diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html index dfc4769d6e4..fa0fa5666f6 100644 --- a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html @@ -40,10 +40,8 @@

- - - This is not a production server! - + + This is not a production server! Do not store any information here that contains personal health information or any other confidential information. This server will be regularly purged and reloaded with fixed test data. diff --git a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html index dfc4769d6e4..fa0fa5666f6 100644 --- a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html @@ -40,10 +40,8 @@

- - - This is not a production server! - + + This is not a production server! Do not store any information here that contains personal health information or any other confidential information. This server will be regularly purged and reloaded with fixed test data. diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 2fca895151a..00000000000 --- a/examples/.gitignore +++ /dev/null @@ -1,125 +0,0 @@ -/target/ - -# Created by https://www.gitignore.io - -### Java ### -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties - - -### Vim ### -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -*.un~ -Session.vim -.netrwhist -*~ - - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties - - - -### Eclipse ### -*.pydevproject -.metadata -.gradle -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.loadpath - -# Eclipse Core -.project - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# JDT-specific (Eclipse Java Development Tools) - -# PDT-specific -.buildpath - -# sbteclipse plugin -.target - -# TeXlipse plugin -.texlipse - diff --git a/examples/pom.xml b/examples/pom.xml deleted file mode 100644 index 9adf692ef09..00000000000 --- a/examples/pom.xml +++ /dev/null @@ -1,133 +0,0 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir - 4.3.0-SNAPSHOT - ../pom.xml - - - hapi-fhir-base-examples - jar - - HAPI FHIR - Examples (for site) - - - - - ca.uhn.hapi.fhir - hapi-fhir-bom - ${project.version} - import - pom - - - - - - - ca.uhn.hapi.fhir - hapi-fhir-base - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu2 - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu2.1 - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu3 - - - ca.uhn.hapi.fhir - hapi-fhir-structures-r4 - - - ca.uhn.hapi.fhir - hapi-fhir-structures-hl7org-dstu2 - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2 - - - ca.uhn.hapi.fhir - hapi-fhir-validation - - - ca.uhn.hapi.fhir - hapi-fhir-converter - - - ca.uhn.hapi.fhir - hapi-fhir-client-okhttp - - - javax.servlet - javax.servlet-api - provided - - - - ca.uhn.hapi.fhir - hapi-fhir-jaxrsserver-base - - - javax.ws.rs - javax.ws.rs-api - 2.0 - provided - - - javax.ejb - ejb-api - 3.0 - provided - - - org.springframework - spring-web - - - ca.uhn.hapi.fhir - hapi-fhir-jpaserver-base - - - - org.slf4j - slf4j-simple - ${slf4j_version} - - - - - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - - true - - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - - diff --git a/examples/src/main/java/example/AuthorizationInterceptors.java b/examples/src/main/java/example/AuthorizationInterceptors.java deleted file mode 100644 index 009b725a11d..00000000000 --- a/examples/src/main/java/example/AuthorizationInterceptors.java +++ /dev/null @@ -1,216 +0,0 @@ -package example; - -import ca.uhn.fhir.interceptor.api.HookParams; -import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -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; -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 ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -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 { - - public class PatientResourceProvider implements IResourceProvider - { - - @Override - public Class getResourceType() { - return Patient.class; - } - - public MethodOutcome create(@ResourceParam Patient thePatient, RequestDetails theRequestDetails) { - - return new MethodOutcome(); // populate this - } - - } - - //START SNIPPET: patientAndAdmin - @SuppressWarnings("ConstantConditions") - public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor { - - @Override - public List buildRuleList(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. - IdDt userIdPatientId = null; - boolean userIsAdmin = false; - String authHeader = theRequestDetails.getHeader("Authorization"); - if ("Bearer dfw98h38r".equals(authHeader)) { - // This user has access only to Patient/1 resources - userIdPatientId = new IdDt("Patient", 1L); - } else if ("Bearer 39ff939jgg".equals(authHeader)) { - // This user has access to everything - userIsAdmin = true; - } else { - // Throw an HTTP 401 - throw new AuthenticationException("Missing or invalid Authorization header value"); - } - - // If the user is a specific patient, we create the following rule chain: - // Allow the user to read anything in their own patient compartment - // Allow the user to write anything in their own patient compartment - // If a client request doesn't pass either of the above, deny it - if (userIdPatientId != null) { - return new RuleBuilder() - .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen() - .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen() - .denyAll() - .build(); - } - - // If the user is an admin, allow everything - if (userIsAdmin) { - return new RuleBuilder() - .allowAll() - .build(); - } - - // By default, deny everything. This should never get hit, but it's - // good to be defensive - return new RuleBuilder() - .denyAll() - .build(); - } - } - //END SNIPPET: patientAndAdmin - - - //START SNIPPET: conditionalUpdate - @Update() - public MethodOutcome update( - @IdParam IdDt theId, - @ResourceParam Patient theResource, - @ConditionalUrlParam String theConditionalUrl, - ServletRequestDetails theRequestDetails, - IInterceptorBroadcaster theInterceptorBroadcaster) { - - // If we're processing a conditional URL... - if (isNotBlank(theConditionalUrl)) { - - // Pretend we've done the conditional processing. Now let's - // notify the interceptors that an update has been performed - // and supply the actual ID that's being updated - IdDt actual = new IdDt("Patient", "1123"); - - } - - // In a real server, perhaps we would process the conditional - // request differently and follow a separate path. Either way, - // let's pretend there is some storage code here. - theResource.setId(theId.withVersion("2")); - - // Notify the interceptor framework when we're about to perform an update. This is - // useful as the authorization interceptor will pick this event up and use it - // to factor into a decision about whether the operation should be allowed to proceed. - IBaseResource previousContents = theResource; - IBaseResource newContents = theResource; - HookParams params = new HookParams() - .add(IBaseResource.class, previousContents) - .add(IBaseResource.class, newContents) - .add(RequestDetails.class, theRequestDetails) - .add(ServletRequestDetails.class, theRequestDetails); - theInterceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, params); - - MethodOutcome retVal = new MethodOutcome(); - retVal.setCreated(true); - retVal.setResource(theResource); - return retVal; - } - //END SNIPPET: conditionalUpdate - - public void authorizeTenantAction() { - //START SNIPPET: authorizeTenantAction - new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - .allow().read().resourcesOfType(Patient.class).withAnyId().forTenantIds("TENANTA").andThen() - .build(); - } - }; - //END SNIPPET: authorizeTenantAction - - - //START SNIPPET: patchAll - new AuthorizationInterceptor(PolicyEnum.DENY) { - @Override - public List buildRuleList(RequestDetails theRequestDetails) { - return new RuleBuilder() - // Authorize patch requests - .allow().patch().allRequests().andThen() - // Authorize actual writes that patch may perform - .allow().write().allResources().inCompartment("Patient", new IdType("Patient/123")).andThen() - .build(); - } - }; - //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 - - -} diff --git a/examples/src/main/java/example/AuthorizingTesterUiClientFactory.java b/examples/src/main/java/example/AuthorizingTesterUiClientFactory.java deleted file mode 100644 index 78c1d399ba0..00000000000 --- a/examples/src/main/java/example/AuthorizingTesterUiClientFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -package example; - -import javax.servlet.http.HttpServletRequest; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; -import ca.uhn.fhir.rest.server.util.ITestingUiClientFactory; - -public class AuthorizingTesterUiClientFactory implements ITestingUiClientFactory { - - @Override - public IGenericClient newClient(FhirContext theFhirContext, HttpServletRequest theRequest, String theServerBaseUrl) { - // Create a client - IGenericClient client = theFhirContext.newRestfulGenericClient(theServerBaseUrl); - - // Register an interceptor which adds credentials - client.registerInterceptor(new BasicAuthInterceptor("someusername", "somepassword")); - - return client; - } - -} diff --git a/examples/src/main/java/example/BundleFetcher.java b/examples/src/main/java/example/BundleFetcher.java deleted file mode 100644 index 9da5300043d..00000000000 --- a/examples/src/main/java/example/BundleFetcher.java +++ /dev/null @@ -1,67 +0,0 @@ -package example; - -import java.util.HashSet; -import java.util.Set; - -import org.hl7.fhir.instance.model.api.IBaseBundle; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.dstu2.resource.RelatedPerson; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -/** - * @author Bill de Beaubien on 1/13/2016. - */ -public class BundleFetcher { - public static void fetchRestOfBundle(IGenericClient theClient, Bundle theBundle) { - // we need to keep track of which resources are already in the bundle so that if other resources (e.g. Practitioner) are _included, - // we don't end up with multiple copies - Set resourcesAlreadyAdded = new HashSet(); - addInitialUrlsToSet(theBundle, resourcesAlreadyAdded); - Bundle partialBundle = theBundle; - for (;;) { - if (partialBundle.getLink(IBaseBundle.LINK_NEXT) != null) { - partialBundle = theClient.loadPage().next(partialBundle).execute(); - addAnyResourcesNotAlreadyPresentToBundle(theBundle, partialBundle, resourcesAlreadyAdded); - } else { - break; - } - } - // the self and next links for the aggregated bundle aren't really valid anymore, so remove them - theBundle.getLink().clear(); - } - - private static void addInitialUrlsToSet(Bundle theBundle, Set theResourcesAlreadyAdded) { - for (Bundle.Entry entry : theBundle.getEntry()) { - theResourcesAlreadyAdded.add(entry.getFullUrl()); - } - } - - private static void addAnyResourcesNotAlreadyPresentToBundle(Bundle theAggregatedBundle, Bundle thePartialBundle, Set theResourcesAlreadyAdded) { - for (Bundle.Entry entry : thePartialBundle.getEntry()) { - if (!theResourcesAlreadyAdded.contains(entry.getFullUrl())) { - theResourcesAlreadyAdded.add(entry.getFullUrl()); - theAggregatedBundle.getEntry().add(entry); - } - } - } - - public static void main(String[] args) throws Exception { - FhirContext ctx = FhirContext.forDstu2(); - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - // use RelatedPerson because there aren't that many on the server - Bundle bundle = client.search().forResource(RelatedPerson.class).returnBundle(Bundle.class).execute(); - BundleFetcher.fetchRestOfBundle(client, bundle); - if (bundle.getTotal() != bundle.getEntry().size()) { - System.out.println("Counts didn't match! Expected " + bundle.getTotal() + " but bundle only had " + bundle.getEntry().size() + " entries!"); - } - -// IParser parser = ctx.newXmlParser().setPrettyPrint(true); -// System.out.println(parser.encodeResourceToString(bundle)); - - } -} - - diff --git a/examples/src/main/java/example/ClientExamples.java b/examples/src/main/java/example/ClientExamples.java deleted file mode 100644 index 25a805d8dd8..00000000000 --- a/examples/src/main/java/example/ClientExamples.java +++ /dev/null @@ -1,224 +0,0 @@ -package example; - -import ca.uhn.fhir.rest.api.CacheControlDirective; -import org.hl7.fhir.dstu3.model.Bundle; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor; -import ca.uhn.fhir.rest.client.api.*; -import ca.uhn.fhir.rest.client.interceptor.*; -import org.hl7.fhir.r4.model.Patient; - -public class ClientExamples { - - public interface IPatientClient extends IBasicClient { - // nothing yet - } - - @SuppressWarnings("unused") - public void createProxy() { - // START SNIPPET: proxy - FhirContext ctx = FhirContext.forDstu2(); - - // Set connections to access the network via the HTTP proxy at - // example.com : 8888 - ctx.getRestfulClientFactory().setProxy("example.com", 8888); - - // If the proxy requires authentication, use the following as well - ctx.getRestfulClientFactory().setProxyCredentials("theUsername", "thePassword"); - - // Create the client - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - // END SNIPPET: proxy - } - - @SuppressWarnings("unused") - public void processMessage() { - // START SNIPPET: processMessage - FhirContext ctx = FhirContext.forDstu3(); - - // Create the client - IGenericClient client = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - - Bundle bundle = new Bundle(); - // ..populate the bundle.. - - Bundle response = client - .operation() - .processMessage() // New operation for sending messages - .setMessageBundle(bundle) - .asynchronous(Bundle.class) - .execute(); - // END SNIPPET: processMessage - } - - @SuppressWarnings("unused") - public void cacheControl() { - FhirContext ctx = FhirContext.forDstu3(); - - // Create the client - IGenericClient client = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - - Bundle bundle = new Bundle(); - // ..populate the bundle.. - - // START SNIPPET: cacheControl - Bundle response = client - .search() - .forResource(Patient.class) - .returnBundle(Bundle.class) - .cacheControl(new CacheControlDirective().setNoCache(true)) // <-- add a directive - .execute(); - // END SNIPPET: cacheControl - } - - @SuppressWarnings("unused") - public void createOkHttp() { - // START SNIPPET: okhttp - FhirContext ctx = FhirContext.forDstu3(); - - // Use OkHttp - ctx.setRestfulClientFactory(new OkHttpRestfulClientFactory(ctx)); - - // Create the client - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - // END SNIPPET: okhttp - } - - @SuppressWarnings("unused") - public void createTimeouts() { - // START SNIPPET: timeouts - FhirContext ctx = FhirContext.forDstu2(); - - // Set how long to try and establish the initial TCP connection (in ms) - ctx.getRestfulClientFactory().setConnectTimeout(20 * 1000); - - // Set how long to block for individual read/write operations (in ms) - ctx.getRestfulClientFactory().setSocketTimeout(20 * 1000); - - // Create the client - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - // END SNIPPET: timeouts - } - - @SuppressWarnings("unused") - public void createSecurity() { - // START SNIPPET: security - // Create a context and get the client factory so it can be configured - FhirContext ctx = FhirContext.forDstu2(); - IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory(); - - // Create an HTTP basic auth interceptor - String username = "foobar"; - String password = "boobear"; - IClientInterceptor authInterceptor = new BasicAuthInterceptor(username, password); - - // If you're usinf an annotation client, use this style to - // register it - IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir"); - annotationClient.registerInterceptor(authInterceptor); - - // If you're using a generic client, use this instead - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - genericClient.registerInterceptor(authInterceptor); - // END SNIPPET: security - } - - @SuppressWarnings("unused") - public void createCookie() { - // START SNIPPET: cookie - // Create a context and get the client factory so it can be configured - FhirContext ctx = FhirContext.forDstu2(); - IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory(); - - // Create a cookie interceptor. This cookie will have the name "mycookie" and - // the value "Chips Ahoy" - CookieInterceptor interceptor = new CookieInterceptor("mycookie=Chips Ahoy"); - - // Register the interceptor with your client (either style) - IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir"); - annotationClient.registerInterceptor(interceptor); - - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - annotationClient.registerInterceptor(interceptor); - // END SNIPPET: cookie - } - - @SuppressWarnings("unused") - public void gzip() { - // START SNIPPET: gzip - // Create a context and get the client factory so it can be configured - FhirContext ctx = FhirContext.forDstu2(); - IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory(); - - // Register the interceptor with your client (either style) - IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir"); - annotationClient.registerInterceptor(new GZipContentInterceptor()); - // END SNIPPET: gzip - } - - @SuppressWarnings("unused") - public void createSecurityBearer() { - // START SNIPPET: securityBearer - // Create a context and get the client factory so it can be configured - FhirContext ctx = FhirContext.forDstu2(); - IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory(); - - // In reality the token would have come from an authorization server - String token = "3w03fj.r3r3t"; - - BearerTokenAuthInterceptor authInterceptor = new BearerTokenAuthInterceptor(token); - - // Register the interceptor with your client (either style) - IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir"); - annotationClient.registerInterceptor(authInterceptor); - - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - annotationClient.registerInterceptor(authInterceptor); - // END SNIPPET: securityBearer - } - - @SuppressWarnings("unused") - public void createLogging() { - { - // START SNIPPET: logging - // Create a context and get the client factory so it can be configured - FhirContext ctx = FhirContext.forDstu2(); - IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory(); - - // Create a logging interceptor - LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); - - // Optionally you may configure the interceptor (by default only - // summary info is logged) - loggingInterceptor.setLogRequestSummary(true); - loggingInterceptor.setLogRequestBody(true); - - // Register the interceptor with your client (either style) - IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir"); - annotationClient.registerInterceptor(loggingInterceptor); - - IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir"); - genericClient.registerInterceptor(loggingInterceptor); - // END SNIPPET: logging - } - - /******************************/ - { - // START SNIPPET: clientConfig - // Create a client - FhirContext ctx = FhirContext.forDstu2(); - IPatientClient client = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/"); - - // Request JSON encoding from the server (_format=json) - client.setEncoding(EncodingEnum.JSON); - - // Request pretty printing from the server (_pretty=true) - client.setPrettyPrint(true); - // END SNIPPET: clientConfig - } - } - -} diff --git a/examples/src/main/java/example/ClientTransactionExamples.java b/examples/src/main/java/example/ClientTransactionExamples.java deleted file mode 100644 index 82ffe9f7639..00000000000 --- a/examples/src/main/java/example/ClientTransactionExamples.java +++ /dev/null @@ -1,94 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.QuantityDt; -import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; -import ca.uhn.fhir.model.dstu2.resource.*; -import ca.uhn.fhir.model.dstu2.valueset.*; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class ClientTransactionExamples { - - public static void main(String[] args) { - conditionalCreate(); - } - - private static void conditionalCreate() { - - //START SNIPPET: conditional - // Create a patient object - Patient patient = new Patient(); - patient.addIdentifier() - .setSystem("http://acme.org/mrns") - .setValue("12345"); - patient.addName() - .addFamily("Jameson") - .addGiven("J") - .addGiven("Jonah"); - patient.setGender(AdministrativeGenderEnum.MALE); - - // Give the patient a temporary UUID so that other resources in - // the transaction can refer to it - patient.setId(IdDt.newRandomUuid()); - - // Create an observation object - Observation observation = new Observation(); - observation.setStatus(ObservationStatusEnum.FINAL); - observation - .getCode() - .addCoding() - .setSystem("http://loinc.org") - .setCode("789-8") - .setDisplay("Erythrocytes [#/volume] in Blood by Automated count"); - observation.setValue( - new QuantityDt() - .setValue(4.12) - .setUnit("10 trillion/L") - .setSystem("http://unitsofmeasure.org") - .setCode("10*12/L")); - - // The observation refers to the patient using the ID, which is already - // set to a temporary UUID - observation.setSubject(new ResourceReferenceDt(patient.getId().getValue())); - - // Create a bundle that will be used as a transaction - Bundle bundle = new Bundle(); - bundle.setType(BundleTypeEnum.TRANSACTION); - - // Add the patient as an entry. This entry is a POST with an - // If-None-Exist header (conditional create) meaning that it - // will only be created if there isn't already a Patient with - // the identifier 12345 - bundle.addEntry() - .setFullUrl(patient.getId().getValue()) - .setResource(patient) - .getRequest() - .setUrl("Patient") - .setIfNoneExist("identifier=http://acme.org/mrns|12345") - .setMethod(HTTPVerbEnum.POST); - - // Add the observation. This entry is a POST with no header - // (normal create) meaning that it will be created even if - // a similar resource already exists. - bundle.addEntry() - .setResource(observation) - .getRequest() - .setUrl("Observation") - .setMethod(HTTPVerbEnum.POST); - - // Log the request - FhirContext ctx = FhirContext.forDstu2(); - System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle)); - - // Create a client and post the transaction to the server - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - Bundle resp = client.transaction().withBundle(bundle).execute(); - - // Log the response - System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - //END SNIPPET: conditional - - } - -} diff --git a/examples/src/main/java/example/CompleteExampleClient.java b/examples/src/main/java/example/CompleteExampleClient.java deleted file mode 100644 index b58a0b47351..00000000000 --- a/examples/src/main/java/example/CompleteExampleClient.java +++ /dev/null @@ -1,66 +0,0 @@ -package example; - -//START SNIPPET: client -import java.io.IOException; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; -import ca.uhn.fhir.model.dstu2.resource.Organization; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.client.api.IRestfulClient; - -public class CompleteExampleClient { - - /** - * This is a simple client interface. It can have many methods for various - * searches but in this case it has only 1. - */ - public static interface ClientInterface extends IRestfulClient { - - /** - * This is translated into a URL similar to the following: - * http://fhir.healthintersections.com.au/open/Patient?identifier=urn:oid:1.2.36.146.595.217.0.1%7C12345 - */ - @Search - List findPatientsForMrn(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier); - - } - - /** - * The main method here will directly call an open FHIR server and retrieve a - * list of resources matching a given criteria, then load a linked resource. - */ - public static void main(String[] args) throws IOException { - - // Create a client factory - FhirContext ctx = FhirContext.forDstu2(); - - // Create the client - String serverBase = "http://fhir.healthintersections.com.au/open"; - ClientInterface client = ctx.newRestfulClient(ClientInterface.class, serverBase); - - // Invoke the client to search for patient - List patients = client.findPatientsForMrn(new IdentifierDt("urn:oid:1.2.36.146.595.217.0.1", "12345")); - - System.out.println("Found " + patients.size() + " patients"); - - // Print a value from the loaded resource - Patient patient = patients.get(0); - System.out.println("Patient Last Name: " + patient.getName().get(0).getFamily().get(0).getValue()); - - // Load a referenced resource - ResourceReferenceDt managingRef = patient.getManagingOrganization(); - Organization org = (Organization) managingRef.loadResource(client); - - // Print organization name - System.out.println(org.getName()); - - } - -} -// END SNIPPET: client - diff --git a/examples/src/main/java/example/ConsentInterceptors.java b/examples/src/main/java/example/ConsentInterceptors.java deleted file mode 100644 index be9ba91ea03..00000000000 --- a/examples/src/main/java/example/ConsentInterceptors.java +++ /dev/null @@ -1,75 +0,0 @@ -package example; - -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.rest.server.interceptor.consent.ConsentOutcome; -import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices; -import ca.uhn.fhir.rest.server.interceptor.consent.IConsentService; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Observation; - -@SuppressWarnings("unused") -public class ConsentInterceptors { - - - //START SNIPPET: service - public class MyConsentService implements IConsentService { - - /** - * Invoked once at the start of every request - */ - @Override - public ConsentOutcome startOperation(RequestDetails theRequestDetails, IConsentContextServices theContextServices) { - // This means that all requests should flow through the consent service - // This has performance implications - If you know that some requests - // don't need consent checking it is a good idea to return - // ConsentOutcome.AUTHORIZED instead for those requests. - return ConsentOutcome.PROCEED; - } - - /** - * Can a given resource be returned to the user? - */ - @Override - public ConsentOutcome canSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { - // In this basic example, we will filter out lab results so that they - // are never disclosed to the user. A real interceptor might do something - // more nuanced. - if (theResource instanceof Observation) { - Observation obs = (Observation)theResource; - if (obs.getCategoryFirstRep().hasCoding("http://hl7.org/fhir/codesystem-observation-category.html", "laboratory")) { - return ConsentOutcome.REJECT; - } - } - - // Otherwise, allow the - return ConsentOutcome.PROCEED; - } - - /** - * Modify resources that are being shown to the user - */ - @Override - public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { - // Don't return the subject for Observation resources - if (theResource instanceof Observation) { - Observation obs = (Observation)theResource; - obs.setSubject(null); - } - return ConsentOutcome.AUTHORIZED; - } - - @Override - public void completeOperationSuccess(RequestDetails theRequestDetails, IConsentContextServices theContextServices) { - // We could write an audit trail entry in here - } - - @Override - public void completeOperationFailure(RequestDetails theRequestDetails, BaseServerResponseException theException, IConsentContextServices theContextServices) { - // We could write an audit trail entry in here - } - } - //END SNIPPET: service - - -} diff --git a/examples/src/main/java/example/ConverterExamples.java b/examples/src/main/java/example/ConverterExamples.java deleted file mode 100644 index 710e8338485..00000000000 --- a/examples/src/main/java/example/ConverterExamples.java +++ /dev/null @@ -1,35 +0,0 @@ -package example; - -import org.hl7.fhir.convertors.conv10_30.Observation10_30; -import org.hl7.fhir.convertors.conv14_30.Questionnaire14_30; -import org.hl7.fhir.exceptions.FHIRException; - -public class ConverterExamples { - - @SuppressWarnings("unused") - public void c1020() throws FHIRException { - //START SNIPPET: 1020 - // Create an input resource to convert - org.hl7.fhir.dstu2.model.Observation input = new org.hl7.fhir.dstu2.model.Observation(); - input.setEncounter(new org.hl7.fhir.dstu2.model.Reference("Encounter/123")); - - // Convert the resource - org.hl7.fhir.dstu3.model.Observation output = Observation10_30.convertObservation(input); - String context = output.getContext().getReference(); - //END SNIPPET: 1020 - } - - @SuppressWarnings("unused") - public void c1420() throws FHIRException { - //START SNIPPET: 1420 - // Create a resource to convert - org.hl7.fhir.dstu2016may.model.Questionnaire input = new org.hl7.fhir.dstu2016may.model.Questionnaire(); - input.setTitle("My title"); - - // Convert the resource - org.hl7.fhir.dstu3.model.Questionnaire output = Questionnaire14_30.convertQuestionnaire(input); - String context = output.getTitle(); - //END SNIPPET: 1420 - } - -} diff --git a/examples/src/main/java/example/Copier.java b/examples/src/main/java/example/Copier.java deleted file mode 100644 index f73c23e1df3..00000000000 --- a/examples/src/main/java/example/Copier.java +++ /dev/null @@ -1,121 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.util.ResourceReferenceInfo; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -@SuppressWarnings("unused") -public class Copier { - private static final Logger ourLog = LoggerFactory.getLogger(Copier.class); - - public static void main(String[] args) { - FhirContext ctx = FhirContext.forDstu3(); - IGenericClient source = ctx.newRestfulGenericClient("http://localhost:8080/baseDstu3"); - IGenericClient target = ctx.newRestfulGenericClient("https://try.smilecdr.com:8000"); - - List resType = Arrays.asList( - "Patient", "Organization", "Encounter", "Procedure", - "Observation", "ResearchSubject", "Specimen", - "ResearchStudy", "Location", "Practitioner" - ); - - List queued = new ArrayList<>(); - Set sent = new HashSet<>(); - for (String next : resType) { - copy(ctx, source, target, next, queued, sent); - } - - while (queued.size() > 0) { - ourLog.info("Have {} queued resources to deliver", queued.size()); - - for (IBaseResource nextQueued : new ArrayList<>(queued)) { - - String missingRef = null; - for (ResourceReferenceInfo nextRefInfo : ctx.newTerser().getAllResourceReferences(nextQueued)) { - String nextRef = nextRefInfo.getResourceReference().getReferenceElement().getValue(); - if (isNotBlank(nextRef) && !sent.contains(nextRef)) { - missingRef = nextRef; - } - } - if (missingRef != null) { - ourLog.info("Can't send {} because of missing ref {}", nextQueued.getIdElement().getIdPart(), missingRef); - continue; - } - - IIdType newId = target - .update() - .resource(nextQueued) - .execute() - .getId(); - - ourLog.info("Copied resource {} and got ID {}", nextQueued.getIdElement().getValue(), newId); - sent.add(nextQueued.getIdElement().toUnqualifiedVersionless().getValue()); - queued.remove(nextQueued); - } - } - - - } - - private static void copy(FhirContext theCtx, IGenericClient theSource, IGenericClient theTarget, String theResType, List theQueued, Set theSent) { - Bundle received = theSource - .search() - .forResource(theResType) - .returnBundle(Bundle.class) - .execute(); - copy(theCtx, theTarget, theResType, theQueued, theSent, received); - - while (received.getLink("next") != null) { - ourLog.info("Fetching next page..."); - received = theSource.loadPage().next(received).execute(); - copy(theCtx, theTarget, theResType, theQueued, theSent, received); - } - - } - - private static void copy(FhirContext theCtx, IGenericClient theTarget, String theResType, List theQueued, Set theSent, Bundle theReceived) { - for (Bundle.BundleEntryComponent nextEntry : theReceived.getEntry()) { - Resource nextResource = nextEntry.getResource(); - nextResource.setId(theResType + "/" + "CR-" + nextResource.getIdElement().getIdPart()); - - boolean haveUnsentReference = false; - for (ResourceReferenceInfo nextRefInfo : theCtx.newTerser().getAllResourceReferences(nextResource)) { - IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement(); - if (nextRef.hasIdPart()) { - String newRef = nextRef.getResourceType() + "/" + "CR-" + nextRef.getIdPart(); - ourLog.info("Changing reference {} to {}", nextRef.getValue(), newRef); - nextRefInfo.getResourceReference().setReference(newRef); - if (!theSent.contains(newRef)) { - haveUnsentReference = true; - } - } - } - - if (haveUnsentReference) { - ourLog.info("Queueing {} for delivery after", nextResource.getId()); - theQueued.add(nextResource); - continue; - } - - IIdType newId = theTarget - .update() - .resource(nextResource) - .execute() - .getId(); - - ourLog.info("Copied resource {} and got ID {}", nextResource.getId(), newId); - theSent.add(nextResource.getIdElement().toUnqualifiedVersionless().getValue()); - } - } - -} diff --git a/examples/src/main/java/example/CustomObservation.java b/examples/src/main/java/example/CustomObservation.java deleted file mode 100644 index 54269735c0e..00000000000 --- a/examples/src/main/java/example/CustomObservation.java +++ /dev/null @@ -1,7 +0,0 @@ -package example; - -import org.hl7.fhir.dstu3.model.Observation; - -public class CustomObservation extends Observation { - -} diff --git a/examples/src/main/java/example/Dstu2Examples.java b/examples/src/main/java/example/Dstu2Examples.java deleted file mode 100644 index 5456c55a522..00000000000 --- a/examples/src/main/java/example/Dstu2Examples.java +++ /dev/null @@ -1,67 +0,0 @@ -package example; - -import java.util.Collection; - -import javax.servlet.ServletException; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; - -@SuppressWarnings("serial") -public class Dstu2Examples { - private Collection resourceProviderList; - - public static void main(String[] args) { - new Dstu2Examples().getResourceTags(); - } - - @SuppressWarnings("unused") - public void getResourceTags() { - // START SNIPPET: context - // Create a DSTU2 context, which will use DSTU2 semantics - FhirContext ctx = FhirContext.forDstu2(); - - // This parser supports DSTU2 - IParser parser = ctx.newJsonParser(); - - // This client supports DSTU2 - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - // END SNIPPET: context - } - - - // START SNIPPET: server - public class MyServer extends RestfulServer - { - - @Override - protected void initialize() throws ServletException { - - // In your initialize method, assign a DSTU2 FhirContext. This - // is all that is required in order to put the server - // into DSTU2 mode - setFhirContext(FhirContext.forDstu2()); - - // Then set resource providers as normal, and do any other - // configuration you need to do. - setResourceProviders(resourceProviderList); - - } - - } - // END SNIPPET: server - - - public void upgrade() { - // START SNIPPET: client - FhirContext ctxDstu2 = FhirContext.forDstu2(); - IGenericClient clientDstu2 = ctxDstu2.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - - // END SNIPPET: client - - } - -} diff --git a/examples/src/main/java/example/ExampleProviders.java b/examples/src/main/java/example/ExampleProviders.java deleted file mode 100644 index 8a41d40a469..00000000000 --- a/examples/src/main/java/example/ExampleProviders.java +++ /dev/null @@ -1,82 +0,0 @@ -package example; - -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.dstu3.model.Bundle; - -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; -import ca.uhn.fhir.rest.server.IResourceProvider; - -@SuppressWarnings(value= {"serial"}) -public class ExampleProviders { - - -//START SNIPPET: plainProvider -public class PlainProvider { - - /** - * This method is a Patient search, but HAPI can not automatically - * determine the resource type so it must be explicitly stated. - */ - @Search(type=Patient.class) - public Bundle searchForPatients(@RequiredParam(name=Patient.SP_NAME) StringDt theName) { - Bundle retVal = new Bundle(); - // perform search - return retVal; - } - -} -//END SNIPPET: plainProvider - - -//START SNIPPET: plainProviderServer -public class ExampleServlet extends ca.uhn.fhir.rest.server.RestfulServer { - - /** - * Constructor - */ - public ExampleServlet() { - /* - * Plain providers are passed to the server in the same way - * as resource providers. You may pass both resource providers - * and and plain providers to the same server if you like. - */ - List plainProviders=new ArrayList(); - plainProviders.add(new PlainProvider()); - registerProviders(plainProviders); - - List resourceProviders = new ArrayList(); - // ...add some resource providers... - registerProviders(resourceProviders); - } - -} -//END SNIPPET: plainProviderServer - - //START SNIPPET: addressStrategy - public class MyServlet extends ca.uhn.fhir.rest.server.RestfulServer { - - /** - * Constructor - */ - public MyServlet() { - - String serverBaseUrl = "http://foo.com/fhir"; - setServerAddressStrategy(new HardcodedServerAddressStrategy(serverBaseUrl)); - - // ...add some resource providers, etc... - List resourceProviders = new ArrayList(); - setResourceProviders(resourceProviders); - } - - } -//END SNIPPET: addressStrategy - - - -} diff --git a/examples/src/main/java/example/ExampleRestfulClient.java b/examples/src/main/java/example/ExampleRestfulClient.java deleted file mode 100644 index 16e05c2304e..00000000000 --- a/examples/src/main/java/example/ExampleRestfulClient.java +++ /dev/null @@ -1,26 +0,0 @@ -package example; - -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.StringDt; - -@SuppressWarnings("unused") -public class ExampleRestfulClient { - -//START SNIPPET: client -public static void main(String[] args) { - FhirContext ctx = FhirContext.forDstu2(); - String serverBase = "http://foo.com/fhirServerBase"; - - // Create the client - IRestfulClient client = ctx.newRestfulClient(IRestfulClient.class, serverBase); - - // Try the client out! This method will invoke the server - List patients = client.getPatient(new StringDt("SMITH")); - -} -//END SNIPPET: client - -} diff --git a/examples/src/main/java/example/ExampleRestfulServlet.java b/examples/src/main/java/example/ExampleRestfulServlet.java deleted file mode 100644 index 34bd915a86d..00000000000 --- a/examples/src/main/java/example/ExampleRestfulServlet.java +++ /dev/null @@ -1,45 +0,0 @@ -package example; - -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; - -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; - -//START SNIPPET: servlet -/** - * In this example, we are using Servlet 3.0 annotations to define - * the URL pattern for this servlet, but we could also - * define this in a web.xml file. - */ -@WebServlet(urlPatterns= {"/fhir/*"}, displayName="FHIR Server") -public class ExampleRestfulServlet extends RestfulServer { - - private static final long serialVersionUID = 1L; - - /** - * The initialize method is automatically called when the servlet is starting up, so it can - * be used to configure the servlet to define resource providers, or set up - * configuration, interceptors, etc. - */ - @Override - protected void initialize() throws ServletException { - /* - * The servlet defines any number of resource providers, and - * configures itself to use them by calling - * setResourceProviders() - */ - List resourceProviders = new ArrayList(); - resourceProviders.add(new RestfulPatientResourceProvider()); - resourceProviders.add(new RestfulObservationResourceProvider()); - setResourceProviders(resourceProviders); - } - -} -//END SNIPPET: servlet - - - diff --git a/examples/src/main/java/example/ExtensionsDstu2.java b/examples/src/main/java/example/ExtensionsDstu2.java deleted file mode 100644 index fe0a272ce5f..00000000000 --- a/examples/src/main/java/example/ExtensionsDstu2.java +++ /dev/null @@ -1,106 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.ExtensionDt; -import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.resource.Questionnaire; -import ca.uhn.fhir.model.dstu2.resource.Questionnaire.GroupQuestion; -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.primitive.CodeDt; -import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.parser.DataFormatException; - -public class ExtensionsDstu2 { - -@SuppressWarnings("unused") -public static void main(String[] args) throws DataFormatException, IOException { - - { - Questionnaire q= new Questionnaire(); - GroupQuestion item = q.getGroup().addQuestion(); - item.setText("Hello"); - - ExtensionDt extension = new ExtensionDt(false, "http://hl7.org/fhir/StructureDefinition/translation"); - item.getTextElement().addUndeclaredExtension(extension); - - extension.addUndeclaredExtension(new ExtensionDt(false, "lang", new CodeDt("es"))); - extension.addUndeclaredExtension(new ExtensionDt(false, "cont", new StringDt("hola"))); - - System.out.println(FhirContext.forDstu2().newJsonParser().setPrettyPrint(true).encodeResourceToString(q)); - } - - -// START SNIPPET: resourceExtension -// Create an example patient -Patient patient = new Patient(); -patient.addIdentifier().setUse(IdentifierUseEnum.OFFICIAL).setSystem("urn:example").setValue("7000135"); - -// Create an extension -ExtensionDt ext = new ExtensionDt(); -ext.setModifier(false); -ext.setUrl("http://example.com/extensions#someext"); -ext.setValue(new DateTimeDt("2011-01-02T11:13:15")); - -// Add the extension to the resource -patient.addUndeclaredExtension(ext); -//END SNIPPET: resourceExtension - - -//START SNIPPET: resourceStringExtension -// Continuing the example from above, we will add a name to the patient, and then -// add an extension to part of that name -HumanNameDt name = patient.addName(); -name.addFamily().setValue("Shmoe"); - -// Add a new "given name", which is of type StringDt -StringDt given = name.addGiven(); -given.setValue("Joe"); - -// Create an extension and add it to the StringDt -ExtensionDt givenExt = new ExtensionDt(false, "http://examples.com#moreext", new StringDt("Hello")); -given.addUndeclaredExtension(givenExt); -//END SNIPPET: resourceStringExtension - -FhirContext ctx = FhirContext.forDstu2(); -String output = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(output); - - -//START SNIPPET: parseExtension -// Get all extensions (modifier or not) for a given URL -List resourceExts = patient.getUndeclaredExtensionsByUrl("http://fooextensions.com#exts"); - -// Get all non-modifier extensions regardless of URL -List nonModExts = patient.getUndeclaredExtensions(); - -//Get all non-modifier extensions regardless of URL -List modExts = patient.getUndeclaredModifierExtensions(); -//END SNIPPET: parseExtension - -} - - -public void foo() { -//START SNIPPET: subExtension -Patient patient = new Patient(); - -// Add an extension (initially with no contents) to the resource -ExtensionDt parent = new ExtensionDt(false, "http://example.com#parent"); -patient.addUndeclaredExtension(parent); - -// Add two extensions as children to the parent extension -ExtensionDt child1 = new ExtensionDt(false, "http://example.com#childOne", new StringDt("value1")); -parent.addUndeclaredExtension(child1); - -ExtensionDt child2 = new ExtensionDt(false, "http://example.com#childTwo", new StringDt("value1")); -parent.addUndeclaredExtension(child2); -//END SNIPPET: subExtension - -} - -} diff --git a/examples/src/main/java/example/ExtensionsDstu3.java b/examples/src/main/java/example/ExtensionsDstu3.java deleted file mode 100644 index 0593761c662..00000000000 --- a/examples/src/main/java/example/ExtensionsDstu3.java +++ /dev/null @@ -1,160 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.List; - -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class ExtensionsDstu3 { - - public void customType() { - -IGenericClient client = FhirContext.forDstu3().newRestfulGenericClient("http://foo"); - -//START SNIPPET: customTypeClientSimple -// Create an example patient -MyPatient custPatient = new MyPatient(); -custPatient.addName().setFamily("Smith").addGiven("John"); -custPatient.setPetName(new StringType("Rover")); // populate the extension - -// Create the resource like normal -client.create().resource(custPatient).execute(); - -// You can also read the resource back like normal -custPatient = client.read().resource(MyPatient.class).withId("123").execute(); -//END SNIPPET: customTypeClientSimple - -//START SNIPPET: customTypeClientSearch -// Perform the search using the custom type -Bundle bundle = client - .search() - .forResource(MyPatient.class) - .returnBundle(Bundle.class) - .execute(); - -// Entries in the return bundle will use the given type -MyPatient pat0 = (MyPatient) bundle.getEntry().get(0).getResource(); -//END SNIPPET: customTypeClientSearch - -//START SNIPPET: customTypeClientSearch2 -//Perform the search using the custom type -bundle = client - .history() - .onInstance(new IdType("Patient/123")) - .andReturnBundle(Bundle.class) - .preferResponseType(MyPatient.class) - .execute(); - -//Entries in the return bundle will use the given type -MyPatient historyPatient0 = (MyPatient) bundle.getEntry().get(0).getResource(); -//END SNIPPET: customTypeClientSearch2 - - } - - public void customTypeDeclared() { - - -//START SNIPPET: customTypeClientDeclared -FhirContext ctx = FhirContext.forDstu3(); - -// Instruct the context that if it receives a resource which -// claims to conform to the given profile (by URL), it should -// use the MyPatient type to parse this resource -ctx.setDefaultTypeForProfile("http://example.com/StructureDefinition/mypatient", MyPatient.class); - -// You can declare as many default types as you like -ctx.setDefaultTypeForProfile("http://foo.com/anotherProfile", CustomObservation.class); - -// Create a client -IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu3"); - -// You can also read the resource back like normal -Patient patient = client.read().resource(Patient.class).withId("123").execute(); -if (patient instanceof MyPatient) { - // If the server supplied a resource which declared to conform - // to the given profile, MyPatient will have been returned so - // process it differently.. -} - -//END SNIPPET: customTypeClientDeclared - - - } - -@SuppressWarnings("unused") -public static void main(String[] args) throws DataFormatException, IOException { - - -// START SNIPPET: resourceExtension -// Create an example patient -Patient patient = new Patient(); -patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135"); - -// Create an extension -Extension ext = new Extension(); -ext.setUrl("http://example.com/extensions#someext"); -ext.setValue(new DateTimeType("2011-01-02T11:13:15")); - -// Add the extension to the resource -patient.addExtension(ext); -//END SNIPPET: resourceExtension - - -//START SNIPPET: resourceStringExtension -// Continuing the example from above, we will add a name to the patient, and then -// add an extension to part of that name -HumanName name = patient.addName(); -name.setFamily("Shmoe"); - -// Add a new "given name", which is of type String -StringType given = name.addGivenElement(); -given.setValue("Joe"); - -// Create an extension and add it to the String -Extension givenExt = new Extension("http://examples.com#moreext", new StringType("Hello")); -given.addExtension(givenExt); -//END SNIPPET: resourceStringExtension - -FhirContext ctx = FhirContext.forDstu3(); -String output = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(output); - - -//START SNIPPET: parseExtension -// Get all extensions (modifier or not) for a given URL -List resourceExts = patient.getExtensionsByUrl("http://fooextensions.com#exts"); - -// Get all non-modifier extensions regardless of URL -List nonModExts = patient.getExtension(); - -//Get all non-modifier extensions regardless of URL -List modExts = patient.getModifierExtension(); -//END SNIPPET: parseExtension - -} - - -public void foo() { -//START SNIPPET: subExtension -Patient patient = new Patient(); - -// Add an extension (initially with no contents) to the resource -Extension parent = new Extension("http://example.com#parent"); -patient.addExtension(parent); - -// Add two extensions as children to the parent extension -Extension child1 = new Extension("http://example.com#childOne", new StringType("value1")); -parent.addExtension(child1); - -Extension child2 = new Extension("http://example.com#chilwo", new StringType("value1")); -parent.addExtension(child2); -//END SNIPPET: subExtension - -} - -} diff --git a/examples/src/main/java/example/FhirContextIntro.java b/examples/src/main/java/example/FhirContextIntro.java deleted file mode 100644 index 0ee77203a77..00000000000 --- a/examples/src/main/java/example/FhirContextIntro.java +++ /dev/null @@ -1,137 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; -import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu2.resource.Observation; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.NameUseEnum; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; - -public class FhirContextIntro { - - @SuppressWarnings("unused") - public static void creatingContext() { -// START SNIPPET: creatingContext -// Create a context for DSTU2 -FhirContext ctxDstu2 = FhirContext.forDstu2(); - -// Alternately, create a context for R4 -FhirContext ctxR4 = FhirContext.forR4(); -// END SNIPPET: creatingContext - - } - - @SuppressWarnings("unused") - public static void creatingContextHl7org() { -// START SNIPPET: creatingContextHl7org -// Create a context for DSTU3 -FhirContext ctx = FhirContext.forDstu3(); - -// Working with RI structures is similar to how it works with the HAPI structures -org.hl7.fhir.dstu3.model.Patient patient = new org.hl7.fhir.dstu3.model.Patient(); -patient.addName().addGiven("John").setFamily("Smith"); -patient.getBirthDateElement().setValueAsString("1998-02-22"); - -// Parsing and encoding works the same way too -String encoded = ctx.newJsonParser().encodeResourceToString(patient); - -// END SNIPPET: creatingContextHl7org - - } - - public static void main(String[] args) throws DataFormatException { - -new FhirContextIntro().encodeMsg(); - - - } - - public void encodeMsg() throws DataFormatException { -FhirContext ctx = new FhirContext(Patient.class, Observation.class); -//START SNIPPET: encodeMsg - -/** - * FHIR model types in HAPI are simple POJOs. To create a new - * one, invoke the default constructor and then - * start populating values. - */ -Patient patient = new Patient(); - -// Add an MRN (a patient identifier) -IdentifierDt id = patient.addIdentifier(); -id.setSystem("http://example.com/fictitious-mrns"); -id.setValue("MRN001"); - -// Add a name -HumanNameDt name = patient.addName(); -name.setUse(NameUseEnum.OFFICIAL); -name.addFamily("Tester"); -name.addGiven("John"); -name.addGiven("Q"); - -// We can now use a parser to encode this resource into a string. -String encoded = ctx.newXmlParser().encodeResourceToString(patient); -System.out.println(encoded); -//END SNIPPET: encodeMsg - -//START SNIPPET: encodeMsgJson -IParser jsonParser = ctx.newJsonParser(); -jsonParser.setPrettyPrint(true); -encoded = jsonParser.encodeResourceToString(patient); -System.out.println(encoded); -//END SNIPPET: encodeMsgJson - - - } - - -public void fluent() throws DataFormatException { -FhirContext ctx = new FhirContext(Patient.class, Observation.class); -String encoded; -//START SNIPPET: encodeMsgFluent -Patient patient = new Patient(); -patient.addIdentifier().setSystem("http://example.com/fictitious-mrns").setValue("MRN001"); -patient.addName().setUse(NameUseEnum.OFFICIAL).addFamily("Tester").addGiven("John").addGiven("Q"); - -encoded = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(encoded); -//END SNIPPET: encodeMsgFluent - -} - - - public static void parseMsg() { -FhirContext ctx = FhirContext.forR4(); - -//START SNIPPET: parseMsg -// The following is an example Patient resource -String msgString = "" - + "
John Cardinal
" - + "" - + "" - + "" - + "
" - + "
"; - -// The hapi context object is used to create a new XML parser -// instance. The parser can then be used to parse (or unmarshall) the -// string message into a Patient object -IParser parser = ctx.newXmlParser(); -Patient patient = parser.parseResource(Patient.class, msgString); - -// The patient object has accessor methods to retrieve all of the -// data which has been parsed into the instance. -String patientId = patient.getIdentifier().get(0).getValue(); -String familyName = patient.getName().get(0).getFamily().get(0).getValue(); -String gender = patient.getGender(); - -System.out.println(patientId); // PRP1660 -System.out.println(familyName); // Cardinal -System.out.println(gender); // M -//END SNIPPET: parseMsg - - } - -} diff --git a/examples/src/main/java/example/FhirDataModel.java b/examples/src/main/java/example/FhirDataModel.java deleted file mode 100644 index d6d521a4586..00000000000 --- a/examples/src/main/java/example/FhirDataModel.java +++ /dev/null @@ -1,224 +0,0 @@ -package example; - -import java.util.*; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import ca.uhn.fhir.model.dstu2.composite.*; -import ca.uhn.fhir.model.dstu2.resource.Observation; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.*; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class FhirDataModel { - - public static void datatypes() { - // START SNIPPET: datatypes - Observation obs = new Observation(); - - // These are all equivalent - obs.setIssued(new InstantDt(new Date())); - obs.setIssued(new Date(), TemporalPrecisionEnum.MILLI); - obs.setIssuedWithMillisPrecision(new Date()); - - // The InstantDt also lets you work with the instant as a Java Date - // object or as a FHIR String. - Date date = obs.getIssuedElement().getValue(); // A date object - String dateString = obs.getIssuedElement().getValueAsString(); // "2014-03-08T12:59:58.068-05:00" - // END SNIPPET: datatypes - - System.out.println(date); - System.out.println(dateString); - - } - - @SuppressWarnings("unused") - public void nonNull() { - // START SNIPPET: nonNull - Observation observation = new Observation(); - - // None of these calls will not return null, but instead create their - // respective - // child elements. - List identifierList = observation.getIdentifier(); - CodeableConceptDt code = observation.getCode(); - StringDt textElement = observation.getCode().getTextElement(); - - // DateTimeDt is a FHIR primitive however, so the following will return - // null - // unless a value has been placed there. - Date active = observation.addIdentifier().getPeriod().getStartElement().getValue(); - // END SNIPPET: nonNull - - } - - @SuppressWarnings("unused") - public static void codes() { - // START SNIPPET: codes - Patient patient = new Patient(); - - // You can set this code using a String if you want. Note that - // for "closed" valuesets (such as the one used for Patient.gender) - // you must use one of the strings defined by the FHIR specification. - // You must not define your own. - patient.getGenderElement().setValue("male"); - - // HAPI also provides Java enumerated types which make it easier to - // deal with coded values. This code achieves the exact same result - // as the code above. - patient.setGender(AdministrativeGenderEnum.MALE); - - // You can also retrieve coded values the same way - String genderString = patient.getGenderElement().getValueAsString(); - AdministrativeGenderEnum genderEnum = patient.getGenderElement().getValueAsEnum(); - - // The following is a shortcut to create - patient.setMaritalStatus(MaritalStatusCodesEnum.M); - // END SNIPPET: codes - - } - - - @SuppressWarnings("unused") - public static void codeableConcepts() { - // START SNIPPET: codeableConcepts - Patient patient = new Patient(); - - // Coded types can naturally be set using plain strings - CodingDt statusCoding = patient.getMaritalStatus().addCoding(); - statusCoding.setSystem("http://hl7.org/fhir/v3/MaritalStatus"); - statusCoding.setCode("M"); - statusCoding.setDisplay("Married"); - - // You could add a second coding to the field if needed too. This - // can be useful if you want to convey the concept using different - // codesystems. - CodingDt secondStatus = patient.getMaritalStatus().addCoding(); - secondStatus.setCode("H"); - secondStatus.setSystem("http://example.com#maritalStatus"); - secondStatus.setDisplay("Happily Married"); - - // CodeableConcept also has a text field meant to convey - // a user readable version of the concepts it conveys. - patient.getMaritalStatus().setText("Happily Married"); - - // There are also accessors for retrieving values - String firstCode = patient.getMaritalStatus().getCoding().get(0).getCode(); - String secondCode = patient.getMaritalStatus().getCoding().get(1).getCode(); - // END SNIPPET: codeableConcepts - - } - - @SuppressWarnings("unused") - public static void codeableConceptEnums() { - // START SNIPPET: codeableConceptEnums - Patient patient = new Patient(); - - // Set the CodeableConcept's first coding to use the code - // and codesystem associated with the M value. - patient.setMaritalStatus(MaritalStatusCodesEnum.M); - - // If you need to set other fields (such as the display name) after - // using the Enum type, you may still do so. - patient.getMaritalStatus().getCodingFirstRep().setDisplay("Married"); - patient.getMaritalStatus().getCodingFirstRep().setVersion("1.0"); - patient.getMaritalStatus().getCodingFirstRep().setUserSelected(true); - - // You can use accessors to retrieve values from CodeableConcept fields - - // Returns "M" - String code = patient.getMaritalStatus().getCodingFirstRep().getCode(); - - // Returns "http://hl7.org/fhir/v3/MaritalStatus". This value was also - // populated via the enum above. - String codeSystem = patient.getMaritalStatus().getCodingFirstRep().getCode(); - - // In many cases, Enum types can be used to retrieve values as well. Note that - // the setter takes a single type, but the getter returns a Set, because the - // field can technicaly contain more than one code and codesystem. BE CAREFUL - // when using this method however, as no Enum will be returned in the case - // that the field contains only a code other than the ones defined by the Enum. - Set status = patient.getMaritalStatus().getValueAsEnum(); - // END SNIPPET: codeableConceptEnums - - } - - - public static void main(String[] args) { - tmp(); - - - datatypes(); - - // START SNIPPET: observation - // Create an Observation instance - Observation observation = new Observation(); - - // Give the observation a status - observation.setStatus(ObservationStatusEnum.FINAL); - - // Give the observation a code (what kind of observation is this) - CodingDt coding = observation.getCode().addCoding(); - coding.setCode("29463-7").setSystem("http://loinc.org").setDisplay("Body Weight"); - - // Create a quantity datatype - QuantityDt value = new QuantityDt(); - value.setValue(83.9).setSystem("http://unitsofmeasure.org").setCode("kg"); - observation.setValue(value); - - // Set the reference range - SimpleQuantityDt low = new SimpleQuantityDt(); - low.setValue(45).setSystem("http://unitsofmeasure.org").setCode("kg"); - observation.getReferenceRangeFirstRep().setLow(low); - SimpleQuantityDt high = new SimpleQuantityDt(); - low.setValue(90).setSystem("http://unitsofmeasure.org").setCode("kg"); - observation.getReferenceRangeFirstRep().setHigh(high); - - // END SNIPPET: observation - - - } - - private static void tmp() { -// Create a FHIR Context -FhirContext ctx = FhirContext.forDstu2(); - -// Create a client -IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - -// Read a patient with the given ID -Patient patient = client - .read() - .resource(Patient.class) - .withId("952975") - .execute(); - -// Print the patient's name -String string = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(string); - - } - - public void namesHard() { - // START SNIPPET: namesHard - Patient patient = new Patient(); - HumanNameDt name = patient.addName(); - StringDt family = name.addFamily(); - family.setValue("Smith"); - StringDt firstName = name.addGiven(); - firstName.setValue("Rob"); - StringDt secondName = name.addGiven(); - secondName.setValue("Bruce"); - // END SNIPPET: namesHard - } - - public void namesEasy() { - // START SNIPPET: namesEasy - Patient patient = new Patient(); - patient.addName().addFamily("Smith").addGiven("Rob").addGiven("Bruce"); - // END SNIPPET: namesEasy - } - -} diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java deleted file mode 100644 index 8366f2c147b..00000000000 --- a/examples/src/main/java/example/GenericClientExample.java +++ /dev/null @@ -1,536 +0,0 @@ -package example; - -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.PerformanceOptionsEnum; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.*; -import ca.uhn.fhir.model.dstu2.resource.OperationOutcome.Issue; -import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; -import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; - -public class GenericClientExample { - public static void deferModelScanning() { - // START SNIPPET: deferModelScanning - // Create a context and configure it for deferred child scanning - FhirContext ctx = FhirContext.forDstu2(); - ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING); - - // Now create a client and use it - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - // END SNIPPET: deferModelScanning - } - - public static void performance() { - // START SNIPPET: dontValidate - // Create a context - FhirContext ctx = FhirContext.forDstu2(); - - // Disable server validation (don't pull the server's metadata first) - ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - - // Now create a client and use it - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - // END SNIPPET: dontValidate - } - - public static void simpleExample() { - // START SNIPPET: simple - // We're connecting to a DSTU1 compliant server in this example - FhirContext ctx = FhirContext.forDstu2(); - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - - // Perform a search - Bundle results = client - .search() - .forResource(Patient.class) - .where(Patient.FAMILY.matches().value("duck")) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - - System.out.println("Found " + results.getEntry().size() + " patients named 'duck'"); - // END SNIPPET: simple - } - - @SuppressWarnings("unused") - public static void fluentSearch() { - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); - { - // START SNIPPET: create - Patient patient = new Patient(); - // ..populate the patient object.. - patient.addIdentifier().setSystem("urn:system").setValue("12345"); - patient.addName().addFamily("Smith").addGiven("John"); - - // Invoke the server create method (and send pretty-printed JSON - // encoding to the server - // instead of the default which is non-pretty printed XML) - MethodOutcome outcome = client.create() - .resource(patient) - .prettyPrint() - .encodedJson() - .execute(); - - // The MethodOutcome object will contain information about the - // response from the server, including the ID of the created - // resource, the OperationOutcome response, etc. (assuming that - // any of these things were provided by the server! They may not - // always be) - IdDt id = (IdDt) outcome.getId(); - System.out.println("Got ID: " + id.getValue()); - // END SNIPPET: create - } - { - Patient patient = new Patient(); - // START SNIPPET: createConditional - // One form - MethodOutcome outcome = client.create() - .resource(patient) - .conditionalByUrl("Patient?identifier=system%7C00001") - .execute(); - - // Another form - MethodOutcome outcome2 = client.create() - .resource(patient) - .conditional() - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - - // This will return Boolean.TRUE if the server responded with an HTTP 201 created, - // otherwise it will return null. - Boolean created = outcome.getCreated(); - - // The ID of the created, or the pre-existing resource - IdDt id = (IdDt) outcome.getId(); - // END SNIPPET: createConditional - } - { - // START SNIPPET: validate - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://hospital.com").setValue("123445"); - patient.addName().addFamily("Smith").addGiven("John"); - - // Validate the resource - MethodOutcome outcome = client.validate() - .resource(patient) - .execute(); - - // The returned object will contain an operation outcome resource - OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome(); - - // If the OperationOutcome has any issues with a severity of ERROR or SEVERE, - // the validation failed. - for (Issue nextIssue : oo.getIssue()) { - if (nextIssue.getSeverityElement().getValueAsEnum().ordinal() >= IssueSeverityEnum.ERROR.ordinal()) { - System.out.println("We failed validation!"); - } - } - // END SNIPPET: validate - } - { - // START SNIPPET: update - Patient patient = new Patient(); - // ..populate the patient object.. - patient.addIdentifier().setSystem("urn:system").setValue("12345"); - patient.addName().addFamily("Smith").addGiven("John"); - - // To update a resource, it should have an ID set (if the resource - // object - // comes from the results of a previous read or search, it will already - // have one though) - patient.setId("Patient/123"); - - // Invoke the server update method - MethodOutcome outcome = client.update() - .resource(patient) - .execute(); - - // The MethodOutcome object will contain information about the - // response from the server, including the ID of the created - // resource, the OperationOutcome response, etc. (assuming that - // any of these things were provided by the server! They may not - // always be) - IdDt id = (IdDt) outcome.getId(); - System.out.println("Got ID: " + id.getValue()); - // END SNIPPET: update - } - { - Patient patient = new Patient(); - // START SNIPPET: updateConditional - client.update() - .resource(patient) - .conditionalByUrl("Patient?identifier=system%7C00001") - .execute(); - - client.update() - .resource(patient) - .conditional() - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - // END SNIPPET: updateConditional - } - { - // START SNIPPET: etagupdate - // First, let's retrive the latest version of a resource - // from the server - Patient patient = client.read().resource(Patient.class).withId("123").execute(); - - // If the server is a version aware server, we should now know the latest version - // of the resource - System.out.println("Version ID: " + patient.getId().getVersionIdPart()); - - // Now let's make a change to the resource - patient.setGender(AdministrativeGenderEnum.FEMALE); - - // Invoke the server update method - Because the resource has - // a version, it will be included in the request sent to - // the server - try { - MethodOutcome outcome = client - .update() - .resource(patient) - .execute(); - } catch (PreconditionFailedException e) { - // If we get here, the latest version has changed - // on the server so our update failed. - } - // END SNIPPET: etagupdate - } - { - // START SNIPPET: conformance - // Retrieve the server's conformance statement and print its - // description - Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); - System.out.println(conf.getDescriptionElement().getValue()); - // END SNIPPET: conformance - } - { - // START SNIPPET: delete - IBaseOperationOutcome resp = client.delete().resourceById(new IdDt("Patient", "1234")).execute(); - - // outcome may be null if the server didn't return one - if (resp != null) { - OperationOutcome outcome = (OperationOutcome) resp; - System.out.println(outcome.getIssueFirstRep().getDetailsElement().getValue()); - } - // END SNIPPET: delete - } - { - // START SNIPPET: deleteConditional - client.delete() - .resourceConditionalByUrl("Patient?identifier=system%7C00001") - .execute(); - - client.delete() - .resourceConditionalByType("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - // END SNIPPET: deleteConditional - } - { - // START SNIPPET: search - ca.uhn.fhir.model.dstu2.resource.Bundle response = client.search() - .forResource(Patient.class) - .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01")) - .and(Patient.CAREPROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health"))) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - // END SNIPPET: search - - // START SNIPPET: searchOr - response = client.search() - .forResource(Patient.class) - .where(Patient.FAMILY.matches().values("Smith", "Smyth")) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchOr - - // START SNIPPET: searchAnd - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .and(Patient.ADDRESS.matches().values("Ontario")) - .and(Patient.ADDRESS.matches().values("Canada")) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchAnd - - // START SNIPPET: searchCompartment - response = client.search() - .forResource(Patient.class) - .withIdAndCompartment("123", "condition") - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - // END SNIPPET: searchCompartment - - // START SNIPPET: searchUrl - String searchUrl = "http://example.com/base/Patient?identifier=foo"; - - // Search URL can also be a relative URL in which case the client's base - // URL will be added to it - searchUrl = "Patient?identifier=foo"; - - response = client.search() - .byUrl(searchUrl) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - // END SNIPPET: searchUrl - - // START SNIPPET: searchSubsetSummary - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .summaryMode(SummaryEnum.TRUE) - .execute(); - // END SNIPPET: searchSubsetSummary - - // START SNIPPET: searchSubsetElements - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .elementsSubset("identifier", "name") // only include the identifier and name - .execute(); - // END SNIPPET: searchSubsetElements - - // START SNIPPET: searchAdv - response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .withTag("http://acme.org/codes", "needs-review") - .include(Patient.INCLUDE_ORGANIZATION.asRecursive()) - .include(Patient.INCLUDE_CAREPROVIDER.asNonRecursive()) - .revInclude(Provenance.INCLUDE_TARGET) - .lastUpdated(new DateRangeParam("2011-01-01", null)) - .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME).limitTo(123) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchAdv - - // START SNIPPET: searchPost - response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("Tester")) - .usingStyle(SearchStyleEnum.POST) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchPost - - // START SNIPPET: searchComposite - response = client.search() - .forResource("Observation") - .where(Observation.CODE_VALUE_DATE - .withLeft(Observation.CODE.exactly().code("FOO$BAR")) - .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01"))) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchComposite - } - { - // START SNIPPET: transaction - List resources = new ArrayList(); - // .. populate this list - note that you can also pass in a populated - // Bundle if you want to create one manually .. - - List response = client.transaction().withResources(resources).execute(); - // END SNIPPET: transaction - } - - { - // START SNIPPET: read - // search for patient 123 - Patient patient = client.read() - .resource(Patient.class) - .withId("123") - .execute(); - // END SNIPPET: read - } - { - // START SNIPPET: vread - // search for patient 123 (specific version 888) - Patient patient = client.read() - .resource(Patient.class) - .withIdAndVersion("123", "888") - .execute(); - // END SNIPPET: vread - } - { - // START SNIPPET: readabsolute - // search for patient 123 on example.com - String url = "http://example.com/fhir/Patient/123"; - Patient patient = client.read() - .resource(Patient.class) - .withUrl(url) - .execute(); - // END SNIPPET: readabsolute - } - - { - // START SNIPPET: etagread - // search for patient 123 - Patient patient = client.read() - .resource(Patient.class) - .withId("123") - .ifVersionMatches("001").returnNull() - .execute(); - if (patient == null) { - // resource has not changed - } - // END SNIPPET: etagread - } - - - - } - - @SuppressWarnings("unused") - public static void history() { - IGenericClient client = FhirContext.forDstu2().newRestfulGenericClient(""); - { - ca.uhn.fhir.model.dstu2.resource.Bundle response; - // START SNIPPET: historyDstu2 - response = client - .history() - .onServer() - .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - // END SNIPPET: historyDstu2 - } - { - ca.uhn.fhir.model.dstu2.resource.Bundle response; - // START SNIPPET: historyFeatures - response = client - .history() - .onServer() - .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .since(new InstantDt("2012-01-01T12:22:32.038Z")) - .count(100) - .execute(); - // END SNIPPET: historyFeatures - } - } - - public static void main(String[] args) { - paging(); - } - private static void paging() { - { - // START SNIPPET: searchPaging - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - - // Perform a search - Bundle resultBundle = client.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("Smith")) - .returnBundle(Bundle.class) - .execute(); - - if (resultBundle.getLink(Bundle.LINK_NEXT) != null) { - - // load next page - Bundle nextPage = client.loadPage().next(resultBundle).execute(); - } - // END SNIPPET: searchPaging - } - } - - @SuppressWarnings("unused") - private static void operationHttpGet() { - // START SNIPPET: operationHttpGet - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdDt("Patient", "1")) - .named("$everything") - .withParameters(inParams) - .useHttpGet() // Use HTTP GET instead of POST - .execute(); - // END SNIPPET: operationHttpGet - } - - @SuppressWarnings("unused") - private static void operation() { - // START SNIPPET: operation - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdDt("Patient", "1")) - .named("$everything") - .withParameters(inParams) - .execute(); - - /* - * Note that the $everything operation returns a Bundle instead - * of a Parameters resource. The client operation methods return a - * Parameters instance however, so HAPI creates a Parameters object - * with a single parameter containing the value. - */ - Bundle responseBundle = (Bundle) outParams.getParameter().get(0).getResource(); - - // Print the response bundle - System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(responseBundle)); - // END SNIPPET: operation - } - - @SuppressWarnings("unused") - private static void operationNoIn() { - // START SNIPPET: operationNoIn - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdDt("Patient", "1")) - .named("$everything") - .withNoParameters(Parameters.class) // No input parameters - .execute(); - // END SNIPPET: operationNoIn - } - -} diff --git a/examples/src/main/java/example/GenomicsUploader.java b/examples/src/main/java/example/GenomicsUploader.java deleted file mode 100644 index f7f87823905..00000000000 --- a/examples/src/main/java/example/GenomicsUploader.java +++ /dev/null @@ -1,61 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import org.hl7.fhir.r4.model.Enumerations; -import org.hl7.fhir.r4.model.SearchParameter; - -public class GenomicsUploader { - - public static void main(String[] theArgs) { - FhirContext ctx = FhirContext.forR4(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseR4"); - client.registerInterceptor(new LoggingInterceptor(false)); - - SearchParameter dnaSequenceVariantName = new SearchParameter(); - dnaSequenceVariantName.setId("SearchParameter/dnaSequenceVariantName"); - dnaSequenceVariantName.setStatus(Enumerations.PublicationStatus.ACTIVE); - dnaSequenceVariantName.addBase("Observation"); - dnaSequenceVariantName.setCode("dnaSequenceVariantName"); - dnaSequenceVariantName.setType(Enumerations.SearchParamType.TOKEN); - dnaSequenceVariantName.setTitle("DNASequenceVariantName"); - dnaSequenceVariantName.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsDNASequenceVariantName')"); - dnaSequenceVariantName.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - client.update().resource(dnaSequenceVariantName).execute(); - - SearchParameter dNAVariantId = new SearchParameter(); - dNAVariantId.setId("SearchParameter/dNAVariantId"); - dNAVariantId.setStatus(Enumerations.PublicationStatus.ACTIVE); - dNAVariantId.addBase("Observation"); - dNAVariantId.setCode("dnaVariantId"); - dNAVariantId.setType(Enumerations.SearchParamType.TOKEN); - dNAVariantId.setTitle("DNAVariantId"); - dNAVariantId.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsDNAVariantId')"); - dNAVariantId.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - client.update().resource(dNAVariantId).execute(); - - SearchParameter gene = new SearchParameter(); - gene.setId("SearchParameter/gene"); - gene.setStatus(Enumerations.PublicationStatus.ACTIVE); - gene.addBase("Observation"); - gene.setCode("gene"); - gene.setType(Enumerations.SearchParamType.TOKEN); - gene.setTitle("Gene"); - gene.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsGene')"); - gene.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - client.update().resource(gene).execute(); - - SearchParameter alleleName = new SearchParameter(); - alleleName.setId("SearchParameter/alleleName"); - alleleName.setStatus(Enumerations.PublicationStatus.ACTIVE); - alleleName.addBase("Observation"); - alleleName.setCode("alleleName"); - alleleName.setType(Enumerations.SearchParamType.TOKEN); - alleleName.setTitle("AlleleName"); - alleleName.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsAlleleName')"); - alleleName.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - client.update().resource(alleleName).execute(); - } - -} diff --git a/examples/src/main/java/example/HttpProxy.java b/examples/src/main/java/example/HttpProxy.java deleted file mode 100644 index 3e26973b7f3..00000000000 --- a/examples/src/main/java/example/HttpProxy.java +++ /dev/null @@ -1,48 +0,0 @@ -package example; - -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.*; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class HttpProxy { - - public static void main(String[] args) { - /* - * This is out ot date - Just keeping - * it in case it's helpful... - */ - final String authUser = "username"; - final String authPassword = "password"; - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope("10.10.10.10", 8080), - new UsernamePasswordCredentials(authUser, authPassword)); - - HttpHost myProxy = new HttpHost("10.10.10.10", 8080); - - - HttpClientBuilder clientBuilder = HttpClientBuilder.create(); - clientBuilder - .setProxy(myProxy) - .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()) - .setDefaultCredentialsProvider(credsProvider) - .disableCookieManagement(); - CloseableHttpClient httpClient = clientBuilder.build(); - - FhirContext ctx = FhirContext.forDstu2(); - String serverBase = "http://spark.furore.com/fhir/"; - ctx.getRestfulClientFactory().setHttpClient(httpClient); - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - - IdDt id = new IdDt("Patient", "123"); - client.read(Patient.class, id); - - } - -} diff --git a/examples/src/main/java/example/IRestfulClient.java b/examples/src/main/java/example/IRestfulClient.java deleted file mode 100644 index d9963f27d62..00000000000 --- a/examples/src/main/java/example/IRestfulClient.java +++ /dev/null @@ -1,56 +0,0 @@ -package example; - -import java.util.List; - -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.client.api.IBasicClient; - -//START SNIPPET: provider -/** - * All RESTful clients must be an interface which extends IBasicClient - */ -public interface IRestfulClient extends IBasicClient { - - /** - * The "@Read" annotation indicates that this method supports the - * read operation. Read operations should return a single resource - * instance. - * - * @param theId - * The read operation takes one parameter, which must be of type - * IdDt and must be annotated with the "@Read.IdParam" annotation. - * @return - * Returns a resource matching this identifier, or null if none exists. - */ - @Read() - public Patient getResourceById(@IdParam IdDt theId); - - /** - * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different methods annotated with - * this annotation, to support many different search criteria. This - * example searches by family name. - * - * @param theIdentifier - * This operation takes one parameter which is the search criteria. It is - * annotated with the "@Required" annotation. This annotation takes one argument, - * a string containing the name of the search criteria. The datatype here - * is StringDt, but there are other possible parameter types depending on the - * specific search criteria. - * @return - * This method returns a list of Patients. This list may contain multiple - * matching resources, or it may also be empty. - */ - @Search() - public List getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName); - -} -//END SNIPPET: provider - - diff --git a/examples/src/main/java/example/IncludesExamples.java b/examples/src/main/java/example/IncludesExamples.java deleted file mode 100644 index 899d095d62d..00000000000 --- a/examples/src/main/java/example/IncludesExamples.java +++ /dev/null @@ -1,58 +0,0 @@ -package example; - -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Organization; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.server.provider.dstu2.Dstu2BundleFactory; - -public class IncludesExamples { - - public static void main(String[] args) { - testSearchForPatients(); - } - - private static void testSearchForPatients() { - List resources = new IncludesExamples().searchForPatients(); - - // Create a bundle with both - FhirContext ctx = FhirContext.forDstu2(); - - Dstu2BundleFactory bf = new Dstu2BundleFactory(ctx); - bf.addRootPropertiesToBundle(null, null, null, null, null, resources.size(), BundleTypeEnum.SEARCHSET, null); - bf.addResourcesToBundle(new ArrayList<>(resources), BundleTypeEnum.SEARCHSET, null, null, null); - IBaseResource b = bf.getResourceBundle(); - - // Encode the bundle - String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(b); - System.out.println(encoded); - } - - // START SNIPPET: addIncludes - @Search - private List searchForPatients() { - // Create an organization - Organization org = new Organization(); - org.setId("Organization/65546"); - org.setName("Test Organization"); - - // Create a patient - Patient patient = new Patient(); - patient.setId("Patient/1333"); - patient.addIdentifier().setSystem("urn:mrns").setValue("253345"); - patient.getManagingOrganization().setResource(org); - - // Here we return only the patient object, which has links to other resources - List retVal = new ArrayList(); - retVal.add(patient); - return retVal; - } - // END SNIPPET: addIncludes - -} diff --git a/examples/src/main/java/example/JaxRsClient.java b/examples/src/main/java/example/JaxRsClient.java deleted file mode 100644 index 581b38c4b88..00000000000 --- a/examples/src/main/java/example/JaxRsClient.java +++ /dev/null @@ -1,28 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.client.JaxRsRestfulClientFactory; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -@SuppressWarnings(value= {"serial"}) -public class JaxRsClient { - -public static void main(String[] args) { -//START SNIPPET: createClient - - // Create a client - FhirContext ctx = FhirContext.forDstu2(); - - // Create an instance of the JAX RS client factory and - // set it on the context - JaxRsRestfulClientFactory clientFactory = new JaxRsRestfulClientFactory(ctx); - ctx.setRestfulClientFactory(clientFactory); - - // This client uses JAX-RS! - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); - -//END SNIPPET: createClient -} - - -} diff --git a/examples/src/main/java/example/JaxRsConformanceProvider.java b/examples/src/main/java/example/JaxRsConformanceProvider.java deleted file mode 100644 index 1f1549f07f7..00000000000 --- a/examples/src/main/java/example/JaxRsConformanceProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package example; - -import java.util.concurrent.ConcurrentHashMap; - -import javax.ejb.EJB; -import javax.ejb.Stateless; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.IResourceProvider; - -/** - * Conformance Rest Service - * - * @author Peter Van Houte - */ - // START SNIPPET: jax-rs-conformance -@Path("") -@Stateless -@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) -public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { - - @EJB - private JaxRsPatientRestProvider provider; - - public JaxRsConformanceProvider() { - super("My Server Description", "My Server Name", "My Server Version"); - } - - @Override - protected ConcurrentHashMap, IResourceProvider> getProviders() { - ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); - map.put(JaxRsConformanceProvider.class, this); - map.put(JaxRsPatientRestProvider.class, provider); - return map; - } -} -// END SNIPPET: jax-rs-conformance diff --git a/examples/src/main/java/example/JaxRsPatientRestProvider.java b/examples/src/main/java/example/JaxRsPatientRestProvider.java deleted file mode 100644 index 6c68f1a6a14..00000000000 --- a/examples/src/main/java/example/JaxRsPatientRestProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -package example; - -import javax.ejb.Local; -import javax.ejb.Stateless; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; -import ca.uhn.fhir.model.dstu2.resource.Parameters; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.*; - -/** - * A demo JaxRs Patient Rest Provider - */ -@Local -@Stateless -// START SNIPPET: jax-rs-provider-construction -@Path("/Patient") -@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) -public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider { - - public JaxRsPatientRestProvider() { - super(JaxRsPatientRestProvider.class); - } -// END SNIPPET: jax-rs-provider-construction - - @Override - public Class getResourceType() { - return Patient.class; - } - - - @Create - public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) { - // create the patient ... - return new MethodOutcome(new IdDt(1L)).setCreated(true); - } - -// START SNIPPET: jax-rs-provider-operation - @GET - @Path("/{id}/$someCustomOperation") - public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { - return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", - RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); - } - - @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { - @OperationParam(name = "return", type = StringDt.class) }) - public Parameters someCustomOperation(@IdParam IdDt myId, @OperationParam(name = "dummy") StringDt dummyInput) { - Parameters parameters = new Parameters(); - parameters.addParameter().setName("return").setValue(new StringDt("My Dummy Result")); - return parameters; - } - // END SNIPPET: jax-rs-provider-operation - - @POST - @Path("/{id}/$someCustomOperation") - public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { - return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", - RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); - } - -} diff --git a/examples/src/main/java/example/Multitenancy.java b/examples/src/main/java/example/Multitenancy.java deleted file mode 100644 index 21f840e379e..00000000000 --- a/examples/src/main/java/example/Multitenancy.java +++ /dev/null @@ -1,50 +0,0 @@ -package example; - -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Patient; - -public class Multitenancy { - -//START SNIPPET: enableUrlBaseTenantIdentificationStrategy - public class MyServer extends RestfulServer { - - @Override - protected void initialize() { - - setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); - - // ... do other initialization ... - } -} -//END SNIPPET: enableUrlBaseTenantIdentificationStrategy - -//START SNIPPET: resourceProvider - public class MyPatientResourceProvider implements IResourceProvider { - - @Override - public Class getResourceType() { - return Patient.class; - } - - @Read - public Patient read(RequestDetails theRequestDetails, @IdParam IdType theId) { - - String tenantId = theRequestDetails.getTenantId(); - String resourceId = theId.getIdPart(); - - // Use these two values to fetch the patient - - return new Patient(); - } -} - -//END SNIPPET: resourceProvider - -} diff --git a/examples/src/main/java/example/MyPatient.java b/examples/src/main/java/example/MyPatient.java deleted file mode 100644 index 02248f0a81d..00000000000 --- a/examples/src/main/java/example/MyPatient.java +++ /dev/null @@ -1,99 +0,0 @@ -package example; - -//START SNIPPET: patientDef -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.dstu3.model.DateTimeType; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.StringType; - -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.api.annotation.Extension; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.util.ElementUtil; - -/** - * Definition class for adding extensions to the built-in - * Patient resource type. - * - * Note the "profile" attribute below, which indicates the URL/ID of the - * profile implemented by this resource. You are not required to supply this, - * but if you do it will be automatically populated in the resource meta - * tag if the resource is returned by a server. - */ -@ResourceDef(name="Patient", profile="http://example.com/StructureDefinition/mypatient") -public class MyPatient extends Patient { - - private static final long serialVersionUID = 1L; - - /** - * Each extension is defined in a field. Any valid HAPI Data Type - * can be used for the field type. Note that the [name=""] attribute - * in the @Child annotation needs to match the name for the bean accessor - * and mutator methods. - */ - @Child(name="petName") - @Extension(url="http://example.com/dontuse#petname", definedLocally=false, isModifier=false) - @Description(shortDefinition="The name of the patient's favourite pet") - private StringType myPetName; - - /** - * The second example extension uses a List type to provide - * repeatable values. Note that a [max=] value has been placed in - * the @Child annotation. - * - * Note also that this extension is a modifier extension - */ - @Child(name="importantDates", max=Child.MAX_UNLIMITED) - @Extension(url="http://example.com/dontuse#importantDates", definedLocally=false, isModifier=true) - @Description(shortDefinition="Some dates of note for this patient") - private List myImportantDates; - - /** - * It is important to override the isEmpty() method, adding a check for any - * newly added fields. - */ - @Override - public boolean isEmpty() { - return super.isEmpty() && ElementUtil.isEmpty(myPetName, myImportantDates); - } - - /******** - * Accessors and mutators follow - * - * IMPORTANT: - * Each extension is required to have an getter/accessor and a stter/mutator. - * You are highly recommended to create getters which create instances if they - * do not already exist, since this is how the rest of the HAPI FHIR API works. - ********/ - - /** Getter for important dates */ - public List getImportantDates() { - if (myImportantDates==null) { - myImportantDates = new ArrayList(); - } - return myImportantDates; - } - - /** Getter for pet name */ - public StringType getPetName() { - if (myPetName == null) { - myPetName = new StringType(); - } - return myPetName; - } - - /** Setter for important dates */ - public void setImportantDates(List theImportantDates) { - myImportantDates = theImportantDates; - } - - /** Setter for pet name */ - public void setPetName(StringType thePetName) { - myPetName = thePetName; - } - -} -//END SNIPPET: patientDef diff --git a/examples/src/main/java/example/MyPatientUse.java b/examples/src/main/java/example/MyPatientUse.java deleted file mode 100644 index d4068a86c7e..00000000000 --- a/examples/src/main/java/example/MyPatientUse.java +++ /dev/null @@ -1,86 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.api.annotation.Extension; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; - -public class MyPatientUse { - - @ResourceDef() - public static class MyPatient extends Patient { - - @Child(name="petName") - @Extension(url="http://example.com/dontuse#petname", definedLocally=false, isModifier=false) - @Description(shortDefinition="The name of the patient's favourite pet") - private StringDt myPetName; - - public StringDt getPetName() { - if(myPetName==null) { - myPetName = new StringDt(); - } - return myPetName; - } - - public void setPetName(StringDt thePetName) { - myPetName = thePetName; - } - - public List getImportantDates() { - if (myImportantDates==null) { - myImportantDates=new ArrayList(); - } - return myImportantDates; - } - - public void setImportantDates(List theImportantDates) { - myImportantDates = theImportantDates; - } - - @Child(name="importantDates", max=Child.MAX_UNLIMITED) - @Extension(url="http://example.com/dontuse#importantDates", definedLocally=false, isModifier=true) - @Description(shortDefinition="Some dates of note for the patient") - private List myImportantDates; - - } - -@SuppressWarnings("unused") -public static void main(String[] args) throws DataFormatException, IOException { -//START SNIPPET: patientUse -MyPatient patient = new MyPatient(); -patient.setPetName(new StringDt("Fido")); -patient.getImportantDates().add(new DateTimeDt("2010-01-02")); -patient.getImportantDates().add(new DateTimeDt("2014-01-26T11:11:11")); - -patient.addName().addFamily("Smith").addGiven("John").addGiven("Quincy").addSuffix("Jr"); - -IParser p = FhirContext.forDstu2().newXmlParser().setPrettyPrint(true); -String messageString = p.encodeResourceToString(patient); - -System.out.println(messageString); -//END SNIPPET: patientUse - -//START SNIPPET: patientParse -IParser parser = FhirContext.forDstu2().newXmlParser(); -MyPatient newPatient = parser.parseResource(MyPatient.class, messageString); -//END SNIPPET: patientParse - -{ - FhirContext ctx2 = FhirContext.forDstu2(); - RuntimeResourceDefinition def = ctx2.getResourceDefinition(patient); - System.out.println(ctx2.newXmlParser().setPrettyPrint(true).encodeResourceToString(def.toProfile())); -} -} - -} diff --git a/examples/src/main/java/example/Narrative.java b/examples/src/main/java/example/Narrative.java deleted file mode 100644 index d8c5c9aa4ce..00000000000 --- a/examples/src/main/java/example/Narrative.java +++ /dev/null @@ -1,40 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.NarrativeStatusEnum; -import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.parser.DataFormatException; - -@SuppressWarnings("unused") -public class Narrative { - -public static void main(String[] args) throws DataFormatException { - -//START SNIPPET: example1 -Patient patient = new Patient(); -patient.addIdentifier().setSystem("urn:foo").setValue("7000135"); -patient.addName().addFamily("Smith").addGiven("John").addGiven("Edward"); -patient.addAddress().addLine("742 Evergreen Terrace").setCity("Springfield").setState("ZZ"); - -FhirContext ctx = FhirContext.forDstu2(); - -// Use the narrative generator -ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); - -// Encode the output, including the narrative -String output = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(output); -//END SNIPPET: example1 - -} - -public void simple() { -//START SNIPPET: simple -Patient pat = new Patient(); -pat.getText().setStatus(NarrativeStatusEnum.GENERATED); -pat.getText().setDiv("
This is the narrative text
this is line 2
"); -//END SNIPPET: simple -} - -} diff --git a/examples/src/main/java/example/NarrativeGenerator.java b/examples/src/main/java/example/NarrativeGenerator.java deleted file mode 100644 index daeab7ae4ae..00000000000 --- a/examples/src/main/java/example/NarrativeGenerator.java +++ /dev/null @@ -1,21 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator; - -@SuppressWarnings("unused") -public class NarrativeGenerator { - - public void testGenerator() { - -//START SNIPPET: gen -FhirContext ctx = FhirContext.forDstu2(); -String propFile = "classpath:/com/foo/customnarrative.properties"; -CustomThymeleafNarrativeGenerator gen = new CustomThymeleafNarrativeGenerator(propFile); - -ctx.setNarrativeGenerator(gen); -//END SNIPPET: gen - - - } -} diff --git a/examples/src/main/java/example/NewInterceptors.java b/examples/src/main/java/example/NewInterceptors.java deleted file mode 100644 index 60da5d43ded..00000000000 --- a/examples/src/main/java/example/NewInterceptors.java +++ /dev/null @@ -1,4 +0,0 @@ -package example; - -public class NewInterceptors { -} diff --git a/examples/src/main/java/example/PagingPatientProvider.java b/examples/src/main/java/example/PagingPatientProvider.java deleted file mode 100644 index efcfaa7ff46..00000000000 --- a/examples/src/main/java/example/PagingPatientProvider.java +++ /dev/null @@ -1,88 +0,0 @@ -package example; - -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.IResourceProvider; - -import javax.annotation.Nonnull; - -@SuppressWarnings("null") -// START SNIPPET: provider -public class PagingPatientProvider implements IResourceProvider { - - /** - * Search for Patient resources matching a given family name - */ - @Search - public IBundleProvider search(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamily) { - final InstantDt searchTime = InstantDt.withCurrentTime(); - - /** - * First, we'll search the database for a set of database row IDs that - * match the given search criteria. That way we can keep just the row IDs - * around, and load the actual resources on demand later as the client - * pages through them. - */ - final List matchingResourceIds = null; // <-- implement this - - /** - * Return a bundle provider which can page through the IDs and return the - * resources that go with them. - */ - return new IBundleProvider() { - - @Override - public Integer size() { - return matchingResourceIds.size(); - } - - @Nonnull - @Override - public List getResources(int theFromIndex, int theToIndex) { - int end = Math.max(theToIndex, matchingResourceIds.size() - 1); - List idsToReturn = matchingResourceIds.subList(theFromIndex, end); - return loadResourcesByIds(idsToReturn); - } - - @Override - public InstantDt getPublished() { - return searchTime; - } - - @Override - public Integer preferredPageSize() { - // Typically this method just returns null - return null; - } - - @Override - public String getUuid() { - return null; - } - }; - } - - /** - * Load a list of patient resources given their IDs - */ - private List loadResourcesByIds(List theIdsToReturn) { - // .. implement this search against the database .. - return null; - } - - @Override - public Class getResourceType() { - return Patient.class; - } - -} -// END SNIPPET: provider diff --git a/examples/src/main/java/example/PagingServer.java b/examples/src/main/java/example/PagingServer.java deleted file mode 100644 index d35ceddd138..00000000000 --- a/examples/src/main/java/example/PagingServer.java +++ /dev/null @@ -1,33 +0,0 @@ -package example; - -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; -import ca.uhn.fhir.rest.server.RestfulServer; - -@SuppressWarnings({ "serial" }) -//START SNIPPET: provider -public class PagingServer extends RestfulServer { - - public PagingServer() { - - /* - * Set the resource providers as always. Here we are using the paging - * provider from the example below, but it is not strictly neccesary - * to use a paging resource provider as well. If a normal resource - * provider is used (one which returns List instead of IBundleProvider) - * then the loaded resources will be stored by the IPagingProvider. - */ - setResourceProviders(new PagingPatientProvider()); - - /* - * Set a paging provider. Here a simple in-memory implementation - * is used, but you may create your own. - */ - FifoMemoryPagingProvider pp = new FifoMemoryPagingProvider(10); - pp.setDefaultPageSize(10); - pp.setMaximumPageSize(100); - setPagingProvider(pp); - - } - -} -//END SNIPPET: provider diff --git a/examples/src/main/java/example/Parser.java b/examples/src/main/java/example/Parser.java deleted file mode 100644 index 7e3802fcb42..00000000000 --- a/examples/src/main/java/example/Parser.java +++ /dev/null @@ -1,42 +0,0 @@ -package example; - -import java.io.IOException; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; - -public class Parser { - -public static void main(String[] args) throws DataFormatException, IOException { - { -//START SNIPPET: disableStripVersions -FhirContext ctx = FhirContext.forDstu2(); -IParser parser = ctx.newJsonParser(); - -// Disable the automatic stripping of versions from references on the parser -parser.setStripVersionsFromReferences(false); -//END SNIPPET: disableStripVersions - -//START SNIPPET: disableStripVersionsCtx -ctx.getParserOptions().setStripVersionsFromReferences(false); -//END SNIPPET: disableStripVersionsCtx - - } - - { -//START SNIPPET: disableStripVersionsField -FhirContext ctx = FhirContext.forDstu2(); -IParser parser = ctx.newJsonParser(); - -// Preserve versions only on these two fields (for the given parser) -parser.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference", "Patient.managingOrganization"); - -// You can also apply this setting to the context so that it will -// flow to all parsers -ctx.getParserOptions().setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference", "Patient.managingOrganization"); -//END SNIPPET: disableStripVersionsField - - } -} -} diff --git a/examples/src/main/java/example/PatchExamples.java b/examples/src/main/java/example/PatchExamples.java deleted file mode 100644 index 43af0984d46..00000000000 --- a/examples/src/main/java/example/PatchExamples.java +++ /dev/null @@ -1,30 +0,0 @@ -package example; - -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.OperationOutcome; - -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.PatchTypeEnum; - - -public class PatchExamples { - - //START SNIPPET: patch - @Patch - public OperationOutcome patientPatch(@IdParam IdType theId, PatchTypeEnum thePatchType, @ResourceParam String theBody) { - - if (thePatchType == PatchTypeEnum.JSON_PATCH) { - // do something - } - if (thePatchType == PatchTypeEnum.XML_PATCH) { - // do something - } - - OperationOutcome retVal = new OperationOutcome(); - retVal.getText().setDivAsString("
OK
"); - return retVal; - } - //END SNIPPET: patch - - -} diff --git a/examples/src/main/java/example/QuickUsage.java b/examples/src/main/java/example/QuickUsage.java deleted file mode 100644 index fffa88ed80f..00000000000 --- a/examples/src/main/java/example/QuickUsage.java +++ /dev/null @@ -1,54 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.client.api.IRestfulClient; - -public class QuickUsage { - -@SuppressWarnings("unused") -public static void main(String[] args) throws DataFormatException, IOException { - -Patient patient = new Patient(); -patient.addIdentifier().setUse(IdentifierUseEnum.OFFICIAL).setSystem("urn:fake:mrns").setValue("7000135"); -patient.addIdentifier().setUse(IdentifierUseEnum.SECONDARY).setSystem("urn:fake:otherids").setValue("3287486"); - -patient.addName().addFamily("Smith").addGiven("John").addGiven("Q").addSuffix("Junior"); - -patient.setGender(AdministrativeGenderEnum.MALE); - - -FhirContext ctx = FhirContext.forDstu2(); -String xmlEncoded = ctx.newXmlParser().encodeResourceToString(patient); -String jsonEncoded = ctx.newJsonParser().encodeResourceToString(patient); - -MyClientInterface client = ctx.newRestfulClient(MyClientInterface.class, "http://foo/fhir"); -IdentifierDt searchParam = new IdentifierDt("urn:someidentifiers", "7000135"); -List clients = client.findPatientsByIdentifier(searchParam); -} - -public interface MyClientInterface extends IRestfulClient -{ - /** A FHIR search */ - @Search - public List findPatientsByIdentifier(@RequiredParam(name="identifier") IdentifierDt theIdentifier); - - /** A FHIR create */ - @Create - public MethodOutcome createPatient(@ResourceParam Patient thePatient); - -} - -} diff --git a/examples/src/main/java/example/RequestCounterInterceptor.java b/examples/src/main/java/example/RequestCounterInterceptor.java deleted file mode 100644 index acf35013d56..00000000000 --- a/examples/src/main/java/example/RequestCounterInterceptor.java +++ /dev/null @@ -1,29 +0,0 @@ -package example; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; - -//START SNIPPET: interceptor -public class RequestCounterInterceptor extends InterceptorAdapter -{ - - private int myRequestCount; - - public int getRequestCount() { - return myRequestCount; - } - - /** - * Override the incomingRequestPreProcessed method, which is called - * for each incoming request before any processing is done - */ - @Override - public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) { - myRequestCount++; - return true; - } - -} -//END SNIPPET: interceptor diff --git a/examples/src/main/java/example/RequestExceptionInterceptor.java b/examples/src/main/java/example/RequestExceptionInterceptor.java deleted file mode 100644 index c041250bd77..00000000000 --- a/examples/src/main/java/example/RequestExceptionInterceptor.java +++ /dev/null @@ -1,36 +0,0 @@ -package example; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; - -//START SNIPPET: interceptor -public class RequestExceptionInterceptor extends InterceptorAdapter -{ - - @Override - public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, - HttpServletResponse theServletResponse) throws ServletException, IOException { - - // HAPI's server exceptions know what the appropriate HTTP status code is - theServletResponse.setStatus(theException.getStatusCode()); - - // Provide a response ourself - theServletResponse.setContentType("text/plain"); - theServletResponse.getWriter().append("Failed to process!"); - theServletResponse.getWriter().close(); - - // Since we handled this response in the interceptor, we must return false - // to stop processing immediately - return false; - } - - -} -//END SNIPPET: interceptor diff --git a/examples/src/main/java/example/ResourceRefs.java b/examples/src/main/java/example/ResourceRefs.java deleted file mode 100644 index b5d81d41595..00000000000 --- a/examples/src/main/java/example/ResourceRefs.java +++ /dev/null @@ -1,36 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Organization; -import ca.uhn.fhir.model.dstu2.resource.Patient; - -public class ResourceRefs { - - private static FhirContext ourCtx = FhirContext.forDstu2(); - - public static void main(String[] args) { - manualContained(); - } - - public static void manualContained() { - // START SNIPPET: manualContained - // Create an organization, and give it a local ID - Organization org = new Organization(); - org.setId("#localOrganization"); - org.getNameElement().setValue("Contained Test Organization"); - - // Create a patient - Patient patient = new Patient(); - patient.setId("Patient/1333"); - patient.addIdentifier().setSystem("urn:mrns").setValue("253345"); - - // Set the reference, and manually add the contained resource - patient.getManagingOrganization().setReference("#localOrganization"); - patient.getContained().getContainedResources().add(org); - - String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); - System.out.println(encoded); - // END SNIPPET: manualContained - } - -} diff --git a/examples/src/main/java/example/RestfulObservationResourceProvider.java b/examples/src/main/java/example/RestfulObservationResourceProvider.java deleted file mode 100644 index 773d7640c7c..00000000000 --- a/examples/src/main/java/example/RestfulObservationResourceProvider.java +++ /dev/null @@ -1,87 +0,0 @@ -package example; - -import java.util.Collections; -import java.util.List; - -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.model.primitive.UriDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.server.IResourceProvider; - -//START SNIPPET: provider -/** - * All resource providers must implement IResourceProvider - */ -public class RestfulObservationResourceProvider implements IResourceProvider { - - /** - * The getResourceType method comes from IResourceProvider, and must - * be overridden to indicate what type of resource this provider - * supplies. - */ - @Override - public Class getResourceType() { - return Patient.class; - } - - /** - * The "@Read" annotation indicates that this method supports the - * read operation. It takes one argument, the Resource type being returned. - * - * @param theId - * The read operation takes one parameter, which must be of type - * IdDt and must be annotated with the "@Read.IdParam" annotation. - * @return - * Returns a resource matching this identifier, or null if none exists. - */ - @Read() - public Patient getResourceById(@IdParam IdDt theId) { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00002"); - patient.addName().addFamily("Test"); - patient.getName().get(0).addGiven("PatientOne"); - patient.setGender(AdministrativeGenderEnum.FEMALE); - return patient; - } - - /** - * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different methods annotated with - * this annotation, to support many different search criteria. This - * example searches by family name. - * - * @param theIdentifier - * This operation takes one parameter which is the search criteria. It is - * annotated with the "@Required" annotation. This annotation takes one argument, - * a string containing the name of the search criteria. The datatype here - * is StringDt, but there are other possible parameter types depending on the - * specific search criteria. - * @return - * This method returns a list of Patients. This list may contain multiple - * matching resources, or it may also be empty. - */ - @Search() - public List getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName) { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00001"); - patient.addName(); - patient.getName().get(0).addFamily("Test"); - patient.getName().get(0).addGiven("PatientOne"); - patient.setGender(AdministrativeGenderEnum.MALE); - return Collections.singletonList(patient); - } - -} -//END SNIPPET: provider diff --git a/examples/src/main/java/example/RestfulPatientResourceProvider.java b/examples/src/main/java/example/RestfulPatientResourceProvider.java deleted file mode 100644 index 3fe42a9ab39..00000000000 --- a/examples/src/main/java/example/RestfulPatientResourceProvider.java +++ /dev/null @@ -1,90 +0,0 @@ -package example; - -import java.util.Collections; -import java.util.List; - -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.UriDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.IResourceProvider; - -//START SNIPPET: provider -/** - * All resource providers must implement IResourceProvider - */ -public class RestfulPatientResourceProvider implements IResourceProvider { - - /** - * The getResourceType method comes from IResourceProvider, and must - * be overridden to indicate what type of resource this provider - * supplies. - */ - @Override - public Class getResourceType() { - return Patient.class; - } - - /** - * The "@Read" annotation indicates that this method supports the - * read operation. Read operations should return a single resource - * instance. - * - * @param theId - * The read operation takes one parameter, which must be of type - * IdDt and must be annotated with the "@Read.IdParam" annotation. - * @return - * Returns a resource matching this identifier, or null if none exists. - */ - @Read() - public Patient getResourceById(@IdParam IdDt theId) { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00002"); - patient.addName().addFamily("Test"); - patient.getName().get(0).addGiven("PatientOne"); - patient.setGender(AdministrativeGenderEnum.FEMALE); - return patient; - } - - /** - * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different methods annotated with - * this annotation, to support many different search criteria. This - * example searches by family name. - * - * @param theFamilyName - * This operation takes one parameter which is the search criteria. It is - * annotated with the "@Required" annotation. This annotation takes one argument, - * a string containing the name of the search criteria. The datatype here - * is StringParam, but there are other possible parameter types depending on the - * specific search criteria. - * @return - * This method returns a list of Patients. This list may contain multiple - * matching resources, or it may also be empty. - */ - @Search() - public List getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamilyName) { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00001"); - patient.addName(); - patient.getName().get(0).addFamily(theFamilyName.getValue()); - patient.getName().get(0).addGiven("PatientOne"); - patient.setGender(AdministrativeGenderEnum.MALE); - return Collections.singletonList(patient); - } - -} -//END SNIPPET: provider - - diff --git a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java b/examples/src/main/java/example/RestfulPatientResourceProviderMore.java deleted file mode 100644 index 8bb1c1eed4d..00000000000 --- a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java +++ /dev/null @@ -1,1036 +0,0 @@ -package example; - -import java.io.IOException; -import java.math.BigDecimal; -import java.util.*; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; -import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.annotation.Count; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.client.api.IBasicClient; -import ca.uhn.fhir.rest.client.api.IRestfulClient; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.exceptions.*; - -@SuppressWarnings("unused") -public abstract class RestfulPatientResourceProviderMore implements IResourceProvider { - - public interface ITestClient extends IBasicClient - { - - @Search - List getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theParam); - - } - -private boolean detectedVersionConflict; -private boolean conflictHappened; -private boolean couldntFindThisId; -private FhirContext myContext; - -//START SNIPPET: searchAll -@Search -public List getAllOrganizations() { - List retVal=new ArrayList(); // populate this - return retVal; -} -//END SNIPPET: searchAll - -//START SNIPPET: updateEtag -@Update -public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) { - String resourceId = theId.getIdPart(); - String versionId = theId.getVersionIdPart(); // this will contain the ETag - - String currentVersion = "1"; // populate this with the current version - - if (!versionId.equals(currentVersion)) { - throw new ResourceVersionConflictException("Expected version " + currentVersion); - } - - // ... perform the update ... - return new MethodOutcome(); - -} -//END SNIPPET: updateEtag - -//START SNIPPET: summaryAndElements -@Search -public List search( - SummaryEnum theSummary, // will receive the summary (no annotation required) - @Elements Set theElements // (requires the @Elements annotation) - ) { - return null; // todo: populate -} -//END SNIPPET: summaryAndElements - -//START SNIPPET: searchCompartment -public class PatientRp implements IResourceProvider { - - @Override - public Class getResourceType() { - return Patient.class; - } - - @Search(compartmentName="Condition") - public List searchCompartment(@IdParam IdType thePatientId) { - List retVal=new ArrayList(); - - // populate this with resources of any type that are a part of the - // "Condition" compartment for the Patient with ID "thePatientId" - - return retVal; - } - - // .. also include other Patient operations .. -} -//END SNIPPET: searchCompartment - - -//START SNIPPET: sort -@Search -public List findPatients( - @RequiredParam(name=Patient.SP_IDENTIFIER) StringParam theParameter, - @Sort SortSpec theSort) { - List retVal=new ArrayList(); // populate this - - // theSort is null unless a _sort parameter is actually provided - if (theSort != null) { - - // The name of the param to sort by - String param = theSort.getParamName(); - - // The sort order, or null - SortOrderEnum order = theSort.getOrder(); - - // This will be populated if a second _sort was specified - SortSpec subSort = theSort.getChain(); - - // ...apply the sort... - } - - return retVal; -} -//END SNIPPET: sort - -//START SNIPPET: underlyingReq -@Search -public List findPatients( - @RequiredParam(name="foo") StringParam theParameter, - HttpServletRequest theRequest, - HttpServletResponse theResponse) { - List retVal=new ArrayList(); // populate this - return retVal; -} -//END SNIPPET: underlyingReq - -//START SNIPPET: referenceSimple -@Search -public List findDiagnosticReportsWithSubjet( - @OptionalParam(name=DiagnosticReport.SP_SUBJECT) ReferenceParam theSubject - ) { - List retVal=new ArrayList(); - - // If the parameter passed in includes a resource type (e.g. ?subject:Patient=123) - // that resource type is available. Here we just check that it is either not provided - // or set to "Patient" - if (theSubject.hasResourceType()) { - String resourceType = theSubject.getResourceType(); - if ("Patient".equals(resourceType) == false) { - throw new InvalidRequestException("Invalid resource type for parameter 'subject': " + resourceType); - } - } - - if (theSubject != null) { - // ReferenceParam extends IdType so all of the resource ID methods are available - String subjectId = theSubject.getIdPart(); - - // .. populate retVal with DiagnosticReport resources having - // subject with id "subjectId" .. - - } - - return retVal; - -} -//END SNIPPET: referenceSimple - - -//START SNIPPET: referenceWithChain -@Search -public List findReportsWithChain( - @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {Patient.SP_FAMILY, Patient.SP_GENDER}) ReferenceParam theSubject - ) { - List retVal=new ArrayList(); - - String chain = theSubject.getChain(); - if (Patient.SP_FAMILY.equals(chain)) { - String familyName = theSubject.getValue(); - // .. populate with reports matching subject family name .. - } - if (Patient.SP_GENDER.equals(chain)) { - String gender = theSubject.getValue(); - // .. populate with reports matching subject gender .. - } - - return retVal; -} -//END SNIPPET: referenceWithChain - - -//START SNIPPET: referenceWithChainCombo -@Search -public List findReportsWithChainCombo ( - @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {"", Patient.SP_FAMILY}) ReferenceParam theSubject - ) { - List retVal=new ArrayList(); - - String chain = theSubject.getChain(); - if (Patient.SP_FAMILY.equals(chain)) { - String familyName = theSubject.getValue(); - // .. populate with reports matching subject family name .. - } - if ("".equals(chain)) { - String resourceId = theSubject.getValue(); - // .. populate with reports matching subject with resource ID .. - } - - return retVal; -} -//END SNIPPET: referenceWithChainCombo - - -//START SNIPPET: referenceWithStaticChain -@Search -public List findObservations( - @RequiredParam(name=Observation.SP_SUBJECT+'.'+Patient.SP_IDENTIFIER) TokenParam theProvider - ) { - - String system = theProvider.getSystem(); - String identifier = theProvider.getValue(); - - // ...Do a search for all observations for the given subject... - - List retVal=new ArrayList(); // populate this - return retVal; - -} -//END SNIPPET: referenceWithStaticChain - - -//START SNIPPET: referenceWithDynamicChain -@Search() -public List findBySubject( - @RequiredParam(name=Observation.SP_SUBJECT, chainWhitelist = {"", Patient.SP_IDENTIFIER, Patient.SP_BIRTHDATE}) ReferenceParam subject - ) { - List observations = new ArrayList(); - - String chain = subject.getChain(); - if (Patient.SP_IDENTIFIER.equals(chain)) { - - // Because the chained parameter "subject.identifier" is actually of type - // "token", we convert the value to a token before processing it. - TokenParam tokenSubject = subject.toTokenParam(myContext); - String system = tokenSubject.getSystem(); - String identifier = tokenSubject.getValue(); - - // TODO: populate all the observations for the identifier - - } else if (Patient.SP_BIRTHDATE.equals(chain)) { - - // Because the chained parameter "subject.birthdate" is actually of type - // "date", we convert the value to a date before processing it. - DateParam dateSubject = subject.toDateParam(myContext); - DateTimeType birthDate = new DateTimeType(dateSubject.getValueAsString()); - - // TODO: populate all the observations for the birthdate - - } else if ("".equals(chain)) { - - String resourceId = subject.getValue(); - // TODO: populate all the observations for the resource id - - } - - return observations; -} -//END SNIPPET: referenceWithDynamicChain - - -//START SNIPPET: read -@Read() -public Patient getResourceById(@IdParam IdType theId) { - Patient retVal = new Patient(); - - // ...populate... - retVal.addIdentifier().setSystem("urn:mrns").setValue("12345"); - retVal.addName().setFamily("Smith").addGiven("Tester").addGiven("Q"); - // ...etc... - - // if you know the version ID of the resource, you should set it and HAPI will - // include it in a Content-Location header - retVal.setId(new IdType("Patient", "123", "2")); - - return retVal; -} -//END SNIPPET: read - -//START SNIPPET: delete -@Delete() -public void deletePatient(@IdParam IdType theId) { - // .. Delete the patient .. - if (couldntFindThisId) { - throw new ResourceNotFoundException("Unknown version"); - } - if (conflictHappened) { - throw new ResourceVersionConflictException("Couldn't delete because [foo]"); - } - // otherwise, delete was successful - return; // can also return MethodOutcome -} -//END SNIPPET: delete - - -//START SNIPPET: deleteConditional -@Delete() -public void deletePatientConditional(@IdParam IdType theId, @ConditionalUrlParam String theConditionalUrl) { - // Only one of theId or theConditionalUrl will have a value depending - // on whether the URL receieved was a logical ID, or a conditional - // search string - if (theId != null) { - // do a normal delete - } else { - // do a conditional delete - } - - // otherwise, delete was successful - return; // can also return MethodOutcome -} -//END SNIPPET: deleteConditional - -//START SNIPPET: history -@History() -public List getPatientHistory( - @IdParam IdType theId, - @Since InstantType theSince, - @At DateRangeParam theAt - ) { - List retVal = new ArrayList(); - - Patient patient = new Patient(); - patient.addName().setFamily("Smith"); - - // Set the ID and version - patient.setId(theId.withVersion("1")); - - // ...populate the rest... - return retVal; -} -//END SNIPPET: history - - -//START SNIPPET: vread -@Read(version=true) -public Patient readOrVread(@IdParam IdType theId) { - Patient retVal = new Patient(); - - if (theId.hasVersionIdPart()) { - // this is a vread - } else { - // this is a read - } - - // ...populate... - - return retVal; -} -//END SNIPPET: vread - -//START SNIPPET: searchStringParam -@Search() -public List searchByLastName(@RequiredParam(name=Patient.SP_FAMILY) StringParam theFamily) { - String valueToMatch = theFamily.getValue(); - - if (theFamily.isExact()) { - // Do an exact match search - } else { - // Do a fuzzy search if possible - } - - // ...populate... - Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:mrns").setValue("12345"); - patient.addName().setFamily("Smith").addGiven("Tester").addGiven("Q"); - // ...etc... - - // Every returned resource must have its logical ID set. If the server - // supports versioning, that should be set too - String logicalId = "4325"; - String versionId = "2"; // optional - patient.setId(new IdType("Patient", logicalId, versionId)); - - /* - * This is obviously a fairly contrived example since we are always - * just returning the same hardcoded patient, but in a real scenario - * you could return as many resources as you wanted, and they - * should actually match the given search criteria. - */ - List retVal = new ArrayList(); - retVal.add(patient); - - return retVal; -} -//END SNIPPET: searchStringParam - -//START SNIPPET: searchNamedQuery -@Search(queryName="namedQuery1") -public List searchByNamedQuery(@RequiredParam(name="someparam") StringParam theSomeParam) { - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchNamedQuery - -//START SNIPPET: searchComposite -@Search() -public List searchByComposite( - @RequiredParam(name=Observation.SP_CODE_VALUE_DATE, compositeTypes= {TokenParam.class, DateParam.class}) - CompositeParam theParam) { - // Each of the two values in the composite param are accessible separately. - // In the case of Observation's name-value-date, the left is a string and - // the right is a date. - TokenParam observationName = theParam.getLeftValue(); - DateParam observationValue = theParam.getRightValue(); - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchComposite - - -//START SNIPPET: searchIdentifierParam -@Search() -public List searchByIdentifier(@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theId) { - String identifierSystem = theId.getSystem(); - String identifier = theId.getValue(); - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchIdentifierParam - -//START SNIPPET: searchOptionalParam -@Search() -public List searchByNames( @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName, - @OptionalParam(name=Patient.SP_GIVEN) StringParam theGivenName ) { - String familyName = theFamilyName.getValue(); - String givenName = theGivenName != null ? theGivenName.getValue() : null; - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchOptionalParam - -//START SNIPPET: searchWithDocs -@Description(shortDefinition="This search finds all patient resources matching a given name combination") -@Search() -public List searchWithDocs( - @Description(shortDefinition="This is the patient's last name - Supports partial matches") - @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName, - - @Description(shortDefinition="This is the patient's given names") - @OptionalParam(name=Patient.SP_GIVEN) StringParam theGivenName ) { - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchWithDocs - - -//START SNIPPET: searchMultiple -@Search() -public List searchByObservationNames( - @RequiredParam(name=Observation.SP_CODE) TokenOrListParam theCodings ) { - - // The list here will contain 0..* codings, and any observations which match any of the - // given codings should be returned - List wantedCodings = theCodings.getValuesAsQueryTokens(); - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchMultiple - - -//START SNIPPET: searchMultipleAnd -@Search() -public List searchByPatientAddress( - @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts ) { - - // StringAndListParam is a container for 0..* StringOrListParam, which is in turn a - // container for 0..* strings. It is a little bit weird to understand at first, but think of the - // StringAndListParam to be an AND list with multiple OR lists inside it. So you will need - // to return results which match at least one string within every OR list. - List wantedCodings = theAddressParts.getValuesAsQueryTokens(); - for (StringOrListParam nextOrList : wantedCodings) { - List queryTokens = nextOrList.getValuesAsQueryTokens(); - // Only return results that match at least one of the tokens in the list below - for (StringParam nextString : queryTokens) { - // ....check for match... - } - } - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: searchMultipleAnd - - -//START SNIPPET: dates -@Search() -public List searchByObservationNames( @RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theDate ) { - ParamPrefixEnum prefix = theDate.getPrefix(); // e.g. gt, le, etc.. - Date date = theDate.getValue(); // e.g. 2011-01-02 - TemporalPrecisionEnum precision = theDate.getPrecision(); // e.g. DAY - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: dates - -public void dateClientExample() { -ITestClient client = provideTc(); -//START SNIPPET: dateClient -DateParam param = new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"); -List response = client.getPatientByDob(param); -//END SNIPPET: dateClient -} - -//START SNIPPET: dateRange -@Search() -public List searchByDateRange( - @RequiredParam(name=Observation.SP_DATE) DateRangeParam theRange ) { - - Date from = theRange.getLowerBoundAsInstant(); - Date to = theRange.getUpperBoundAsInstant(); - - List retVal = new ArrayList(); - // ...populate... - return retVal; -} -//END SNIPPET: dateRange - - -private ITestClient provideTc() { - return null; -} -@Override -public Class getResourceType() { - return null; -} - - - -//START SNIPPET: pathSpec -@Search() -public List getDiagnosticReport( - @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) - TokenParam theIdentifier, - - @IncludeParam(allow= {"DiagnosticReport:subject"}) - Set theIncludes ) { - - List retVal = new ArrayList(); - - // Assume this method exists and loads the report from the DB - DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier); - - // If the client has asked for the subject to be included: - if (theIncludes.contains(new Include("DiagnosticReport:subject"))) { - - // The resource reference should contain the ID of the patient - IIdType subjectId = report.getSubject().getReferenceElement(); - - // So load the patient ID and return it - Patient subject = loadSomePatientFromDatabase(subjectId); - report.getSubject().setResource(subject); - - } - - retVal.add(report); - return retVal; -} -//END SNIPPET: pathSpec - -//START SNIPPET: revInclude -@Search() -public List getDiagnosticReport( - @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) - TokenParam theIdentifier, - - @IncludeParam() - Set theIncludes, - - @IncludeParam(reverse=true) - Set theReverseIncludes - ) { - -return new ArrayList(); // populate this -} -//END SNIPPET: revInclude - -//START SNIPPET: pathSpecSimple -@Search() -public List getDiagnosticReport( - @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) - TokenParam theIdentifier, - - @IncludeParam(allow= {"DiagnosticReport:subject"}) - String theInclude ) { - - List retVal = new ArrayList(); - - // Assume this method exists and loads the report from the DB - DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier); - - // If the client has asked for the subject to be included: - if ("DiagnosticReport:subject".equals(theInclude)) { - - // The resource reference should contain the ID of the patient - IIdType subjectId = report.getSubject().getReferenceElement(); - - // So load the patient ID and return it - Patient subject = loadSomePatientFromDatabase(subjectId); - report.getSubject().setResource(subject); - - } - - retVal.add(report); - return retVal; -} -//END SNIPPET: pathSpecSimple - -//START SNIPPET: quantity -@Search() -public List getObservationsByQuantity( - @RequiredParam(name=Observation.SP_VALUE_QUANTITY) QuantityParam theQuantity) { - - List retVal = new ArrayList(); - - ParamPrefixEnum prefix = theQuantity.getPrefix(); - BigDecimal value = theQuantity.getValue(); - String units = theQuantity.getUnits(); - // .. Apply these parameters .. - - // ... populate ... - return retVal; -} -//END SNIPPET: quantity - -private DiagnosticReport loadSomeDiagnosticReportFromDatabase(TokenParam theIdentifier) { - return null; -} - -private Patient loadSomePatientFromDatabase(IIdType theId) { - return null; -} - - -//START SNIPPET: create -@Create -public MethodOutcome createPatient(@ResourceParam Patient thePatient) { - - /* - * First we might want to do business validation. The UnprocessableEntityException - * results in an HTTP 422, which is appropriate for business rule failure - */ - if (thePatient.getIdentifierFirstRep().isEmpty()) { - /* It is also possible to pass an OperationOutcome resource - * to the UnprocessableEntityException if you want to return - * a custom populated OperationOutcome. Otherwise, a simple one - * is created using the string supplied below. - */ - throw new UnprocessableEntityException("No identifier supplied"); - } - - // Save this patient to the database... - savePatientToDatabase(thePatient); - - // This method returns a MethodOutcome object which contains - // the ID (composed of the type Patient, the logical ID 3746, and the - // version ID 1) - MethodOutcome retVal = new MethodOutcome(); - retVal.setId(new IdType("Patient", "3746", "1")); - - // You can also add an OperationOutcome resource to return - // This part is optional though: - OperationOutcome outcome = new OperationOutcome(); - outcome.addIssue().setDiagnostics("One minor issue detected"); - retVal.setOperationOutcome(outcome); - - return retVal; -} -//END SNIPPET: create - - -//START SNIPPET: createConditional -@Create -public MethodOutcome createPatientConditional( - @ResourceParam Patient thePatient, - @ConditionalUrlParam String theConditionalUrl) { - - if (theConditionalUrl != null) { - // We are doing a conditional create - - // populate this with the ID of the existing resource which - // matches the conditional URL - return new MethodOutcome(); - } else { - // We are doing a normal create - - // populate this with the ID of the newly created resource - return new MethodOutcome(); - } - -} -//END SNIPPET: createConditional - - -//START SNIPPET: createClient -@Create -public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient); -//END SNIPPET: createClient - -//START SNIPPET: updateConditional -@Update -public MethodOutcome updatePatientConditional( - @ResourceParam Patient thePatient, - @IdParam IdType theId, - @ConditionalUrlParam String theConditional) { - - // Only one of theId or theConditional will have a value and the other will be null, - // depending on the URL passed into the server. - if (theConditional != null) { - // Do a conditional update. theConditional will have a value like "Patient?identifier=system%7C00001" - } else { - // Do a normal update. theId will have the identity of the resource to update - } - - return new MethodOutcome(); // populate this -} -//END SNIPPET: updateConditional - -//START SNIPPET: updatePrefer -@Update -public MethodOutcome updatePatientPrefer( - @ResourceParam Patient thePatient, - @IdParam IdType theId) { - - // Save the patient to the database - - // Update the version and last updated time on the resource - IdType updatedId = theId.withVersion("123"); - thePatient.setId(updatedId); - InstantType lastUpdated = InstantType.withCurrentTime(); - thePatient.getMeta().setLastUpdatedElement(lastUpdated); - - // Add the resource to the outcome, so that it can be returned by the server - // if the client requests it - MethodOutcome outcome = new MethodOutcome(); - outcome.setId(updatedId); - outcome.setResource(thePatient); - return outcome; -} -//END SNIPPET: updatePrefer - -//START SNIPPET: updateRaw -@Update -public MethodOutcome updatePatientWithRawValue ( - @ResourceParam Patient thePatient, - @IdParam IdType theId, - @ResourceParam String theRawBody, - @ResourceParam EncodingEnum theEncodingEnum) { - - // Here, thePatient will have the parsed patient body, but - // theRawBody will also have the raw text of the resource - // being created, and theEncodingEnum will tell you which - // encoding was used - - return new MethodOutcome(); // populate this -} -//END SNIPPET: updateRaw - -//START SNIPPET: update -@Update -public MethodOutcome updatePatient(@IdParam IdType theId, @ResourceParam Patient thePatient) { - - /* - * First we might want to do business validation. The UnprocessableEntityException - * results in an HTTP 422, which is appropriate for business rule failure - */ - if (thePatient.getIdentifierFirstRep().isEmpty()) { - /* It is also possible to pass an OperationOutcome resource - * to the UnprocessableEntityException if you want to return - * a custom populated OperationOutcome. Otherwise, a simple one - * is created using the string supplied below. - */ - throw new UnprocessableEntityException("No identifier supplied"); - } - - String versionId = theId.getVersionIdPart(); - if (versionId != null) { - // If the client passed in a version number in an If-Match header, they are - // doing a version-aware update. You may wish to throw an exception if the supplied - // version is not the latest version. Note that as of DSTU2 the FHIR specification uses - // ETags and If-Match to handle version aware updates, so PreconditionFailedException (HTTP 412) - // is used instead of ResourceVersionConflictException (HTTP 409) - if (detectedVersionConflict) { - throw new PreconditionFailedException("Unexpected version"); - } - } - - // Save this patient to the database... - savePatientToDatabase(theId, thePatient); - - // This method returns a MethodOutcome object which contains - // the ID and Version ID for the newly saved resource - MethodOutcome retVal = new MethodOutcome(); - String newVersion = "2"; // may be null if the server is not version aware - retVal.setId(theId.withVersion(newVersion)); - - // You can also add an OperationOutcome resource to return - // This part is optional though: - OperationOutcome outcome = new OperationOutcome(); - outcome.addIssue().setDiagnostics("One minor issue detected"); - retVal.setOperationOutcome(outcome); - - // If your server supports creating resources during an update if they don't already exist - // (this is not mandatory and may not be desirable anyhow) you can flag in the response - // that this was a creation as follows: - // retVal.setCreated(true); - - return retVal; -} -//END SNIPPET: update - -//START SNIPPET: updateClient -@Update -public abstract MethodOutcome updateSomePatient(@IdParam IdType theId, @ResourceParam Patient thePatient); -//END SNIPPET: updateClient - -//START SNIPPET: validate -@Validate -public MethodOutcome validatePatient(@ResourceParam Patient thePatient, - @Validate.Mode ValidationModeEnum theMode, - @Validate.Profile String theProfile) { - - // Actually do our validation: The UnprocessableEntityException - // results in an HTTP 422, which is appropriate for business rule failure - if (thePatient.getIdentifierFirstRep().isEmpty()) { - /* It is also possible to pass an OperationOutcome resource - * to the UnprocessableEntityException if you want to return - * a custom populated OperationOutcome. Otherwise, a simple one - * is created using the string supplied below. - */ - throw new UnprocessableEntityException("No identifier supplied"); - } - - // This method returns a MethodOutcome object - MethodOutcome retVal = new MethodOutcome(); - - // You may also add an OperationOutcome resource to return - // This part is optional though: - OperationOutcome outcome = new OperationOutcome(); - outcome.addIssue().setSeverity(IssueSeverity.WARNING).setDiagnostics("One minor issue detected"); - retVal.setOperationOutcome(outcome); - - return retVal; -} -//END SNIPPET: validate - - - - -public static void main(String[] args) throws DataFormatException, IOException { -//nothing -} - - -private void savePatientToDatabase(Patient thePatient) { - // nothing -} -private void savePatientToDatabase(IdType theId, Patient thePatient) { - // nothing -} - -//START SNIPPET: metadataProvider -public class CapabilityStatementProvider { - - @Metadata - public CapabilityStatement getServerMetadata() { - CapabilityStatement retVal = new CapabilityStatement(); - // ..populate.. - return retVal; - } - -} -//END SNIPPET: metadataProvider - - - -//START SNIPPET: metadataClient -public interface MetadataClient extends IRestfulClient { - - @Metadata - CapabilityStatement getServerMetadata(); - - // ....Other methods can also be added as usual.... - -} -//END SNIPPET: metadataClient - -//START SNIPPET: historyClient -public interface HistoryClient extends IBasicClient { - /** Server level (history of ALL resources) */ - @History - Bundle getHistoryServer(); - - /** Type level (history of all resources of a given type) */ - @History(type=Patient.class) - Bundle getHistoryPatientType(); - - /** Instance level (history of a specific resource instance by type and ID) */ - @History(type=Patient.class) - Bundle getHistoryPatientInstance(@IdParam IdType theId); - - /** - * Either (or both) of the "since" and "count" paramaters can - * also be included in any of the methods above. - */ - @History - Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount); - -} -//END SNIPPET: historyClient - - -public void bbbbb() throws DataFormatException, IOException { -//START SNIPPET: metadataClientUsage -FhirContext ctx = FhirContext.forDstu2(); -MetadataClient client = ctx.newRestfulClient(MetadataClient.class, "http://spark.furore.com/fhir"); -CapabilityStatement metadata = client.getServerMetadata(); -System.out.println(ctx.newXmlParser().encodeResourceToString(metadata)); -//END SNIPPET: metadataClientUsage -} - -//START SNIPPET: readTags -@Read() -public Patient readPatient(@IdParam IdType theId) { - Patient retVal = new Patient(); - - // ..populate demographics, contact, or anything else you usually would.. - - // Populate some tags - retVal.getMeta().addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example - retVal.getMeta().addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example - - return retVal; -} -//END SNIPPET: readTags - -//START SNIPPET: clientReadInterface -private interface IPatientClient extends IBasicClient -{ - /** Read a patient from a server by ID */ - @Read - Patient readPatient(@IdParam IdType theId); - - // Only one method is shown here, but many methods may be - // added to the same client interface! -} -//END SNIPPET: clientReadInterface - -public void clientRead() { -//START SNIPPET: clientReadTags -IPatientClient client = FhirContext.forDstu2().newRestfulClient(IPatientClient.class, "http://foo/fhir"); -Patient patient = client.readPatient(new IdType("1234")); - -// Access the tag list -List tagList = patient.getMeta().getTag(); -for (Coding next : tagList) { - // ..process the tags somehow.. -} -//END SNIPPET: clientReadTags - -//START SNIPPET: clientCreateTags -Patient newPatient = new Patient(); - -// Populate the resource object -newPatient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setValue("123"); -newPatient.addName().setFamily("Jones").addGiven("Frank"); - -// Populate some tags -newPatient.getMeta().addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example -newPatient.getMeta().addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example - -// ...invoke the create method on the client... -//END SNIPPET: clientCreateTags -} - -//START SNIPPET: createTags -@Create -public MethodOutcome createPatientResource(@ResourceParam Patient thePatient) { - - // ..save the resouce.. - IdType id = new IdType("123"); // the new databse primary key for this resource - - // Get the tag list - List tags = thePatient.getMeta().getTag(); - for (Coding tag : tags) { - // process/save each tag somehow - } - - return new MethodOutcome(id); -} -//END SNIPPET: createTags - -//START SNIPPET: transaction -@Transaction -public Bundle transaction(@TransactionParam Bundle theInput) { - for (BundleEntryComponent nextEntry : theInput.getEntry()) { - // Process entry - } - - Bundle retVal = new Bundle(); - // Populate return bundle - return retVal; -} -//END SNIPPET: transaction - - -} - - diff --git a/examples/src/main/java/example/SecurityInterceptors.java b/examples/src/main/java/example/SecurityInterceptors.java deleted file mode 100644 index a2e8851fa56..00000000000 --- a/examples/src/main/java/example/SecurityInterceptors.java +++ /dev/null @@ -1,66 +0,0 @@ -package example; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.codec.binary.Base64; - -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; -import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; - -public class SecurityInterceptors { - -// START SNIPPET: basicAuthInterceptor -public class BasicSecurityInterceptor extends InterceptorAdapter -{ - - /** - * This interceptor implements HTTP Basic Auth, which specifies that - * a username and password are provided in a header called Authorization. - */ - @Override - public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { - String authHeader = theRequest.getHeader("Authorization"); - - // The format of the header must be: - // Authorization: Basic [base64 of username:password] - if (authHeader == null || authHeader.startsWith("Basic ") == false) { - throw new AuthenticationException("Missing or invalid Authorization header"); - } - - String base64 = authHeader.substring("Basic ".length()); - String base64decoded = new String(Base64.decodeBase64(base64)); - String[] parts = base64decoded.split("\\:"); - - String username = parts[0]; - String password = parts[1]; - - /* - * Here we test for a hardcoded username & password. This is - * not typically how you would implement this in a production - * system of course.. - */ - if (!username.equals("someuser") || !password.equals("thepassword")) { - throw new AuthenticationException("Invalid username or password"); - } - - // Return true to allow the request to proceed - return true; - } - - -} -//END SNIPPET: basicAuthInterceptor - - - - public void basicAuthInterceptorRealm() { - //START SNIPPET: basicAuthInterceptorRealm - AuthenticationException ex = new AuthenticationException(); - ex.addAuthenticateHeaderForRealm("myRealm"); - throw ex; - //END SNIPPET: basicAuthInterceptorRealm - } - -} diff --git a/examples/src/main/java/example/ServerETagExamples.java b/examples/src/main/java/example/ServerETagExamples.java deleted file mode 100644 index b85f42c39ad..00000000000 --- a/examples/src/main/java/example/ServerETagExamples.java +++ /dev/null @@ -1,29 +0,0 @@ -package example; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; - -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import ca.uhn.fhir.rest.server.RestfulServer; - -@SuppressWarnings("serial") -public class ServerETagExamples { - - // START SNIPPET: disablingETags - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class RestfulServerWithLogging extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - // ... define your resource providers here ... - - // ETag support is enabled by default - setETagSupport(ETagSupportEnum.ENABLED); - } - - } - // END SNIPPET: disablingETags - - - -} diff --git a/examples/src/main/java/example/ServerExceptionsExample.java b/examples/src/main/java/example/ServerExceptionsExample.java deleted file mode 100644 index 7506599da7a..00000000000 --- a/examples/src/main/java/example/ServerExceptionsExample.java +++ /dev/null @@ -1,33 +0,0 @@ -package example; - -import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; -import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; - -public abstract class ServerExceptionsExample implements IResourceProvider { - -private boolean databaseIsDown; - -//START SNIPPET: returnOO -@Read -public Patient read(@IdParam IdDt theId) { - if (databaseIsDown) { - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().setSeverity(IssueSeverityEnum.FATAL).setDetails("Database is down"); - throw new InternalErrorException("Database is down", oo); - } - - Patient patient = new Patient(); // populate this - return patient; -} -//END SNIPPET: returnOO - - -} - - diff --git a/examples/src/main/java/example/ServerInterceptors.java b/examples/src/main/java/example/ServerInterceptors.java deleted file mode 100644 index fdfc5311ba4..00000000000 --- a/examples/src/main/java/example/ServerInterceptors.java +++ /dev/null @@ -1,80 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.ExtensionDt; -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.parser.DataFormatException; - -public class ServerInterceptors { - -@SuppressWarnings("unused") -public static void main(String[] args) throws DataFormatException, IOException { - - -// START SNIPPET: resourceExtension -// Create an example patient -Patient patient = new Patient(); -patient.addIdentifier().setUse(IdentifierUseEnum.OFFICIAL).setSystem("urn:example").setValue("7000135"); - -// Create an extension -ExtensionDt ext = new ExtensionDt(); -ext.setModifier(false); -ext.setUrl("http://example.com/extensions#someext"); -ext.setValue(new DateTimeDt("2011-01-02T11:13:15")); - -// Add the extension to the resource -patient.addUndeclaredExtension(ext); -//END SNIPPET: resourceExtension - - -//START SNIPPET: resourceStringExtension -HumanNameDt name = patient.addName(); -name.addFamily().setValue("Shmoe"); -StringDt given = name.addGiven(); -given.setValue("Joe"); -ExtensionDt ext2 = new ExtensionDt(false, "http://examples.com#moreext", new StringDt("Hello")); -given.addUndeclaredExtension(ext2); -//END SNIPPET: resourceStringExtension - -String output = FhirContext.forDstu2().newXmlParser().setPrettyPrint(true).encodeResourceToString(patient); -System.out.println(output); - - -//START SNIPPET: parseExtension -// Get all extensions (modifier or not) for a given URL -List resourceExts = patient.getUndeclaredExtensionsByUrl("http://fooextensions.com#exts"); - -// Get all non-modifier extensions regardless of URL -List nonModExts = patient.getUndeclaredExtensions(); - -//Get all non-modifier extensions regardless of URL -List modExts = patient.getUndeclaredModifierExtensions(); -//END SNIPPET: parseExtension - -} - - -public void foo() { -//START SNIPPET: subExtension -Patient patient = new Patient(); - -ExtensionDt parent = new ExtensionDt(false, "http://example.com#parent"); -patient.addUndeclaredExtension(parent); - -ExtensionDt child1 = new ExtensionDt(false, "http://example.com#childOne", new StringDt("value1")); -parent.addUndeclaredExtension(child1); - -ExtensionDt child2 = new ExtensionDt(false, "http://example.com#childTwo", new StringDt("value1")); -parent.addUndeclaredExtension(child2); -//END SNIPPET: subExtension - -} - -} diff --git a/examples/src/main/java/example/ServerMetadataExamples.java b/examples/src/main/java/example/ServerMetadataExamples.java deleted file mode 100644 index 20b5e98b838..00000000000 --- a/examples/src/main/java/example/ServerMetadataExamples.java +++ /dev/null @@ -1,41 +0,0 @@ -package example; - -import ca.uhn.fhir.model.api.Tag; -import ca.uhn.fhir.rest.annotation.Search; -import org.hl7.fhir.r4.model.InstantType; -import org.hl7.fhir.r4.model.Patient; - -import java.util.ArrayList; -import java.util.List; - -public class ServerMetadataExamples { - - // START SNIPPET: serverMethod - @Search - public List getAllPatients() { - ArrayList retVal = new ArrayList(); - - // Create a patient to return - Patient patient = new Patient(); - retVal.add(patient); - patient.setId("Patient/123"); - patient.addName().setFamily("Smith").addGiven("John"); - - // Add tags - patient.getMeta().addTag() - .setSystem(Tag.HL7_ORG_FHIR_TAG) - .setCode("some_tag") - .setDisplay("Some tag"); - patient.getMeta().addTag() - .setSystem(Tag.HL7_ORG_FHIR_TAG) - .setCode("another_tag") - .setDisplay("Another tag"); - - // Set the last updated date - patient.getMeta().setLastUpdatedElement(new InstantType("2011-02-22T11:22:00.0122Z")); - - return retVal; - } - // END SNIPPET: serverMethod - -} diff --git a/examples/src/main/java/example/ServerOperations.java b/examples/src/main/java/example/ServerOperations.java deleted file mode 100644 index 5d978867a0a..00000000000 --- a/examples/src/main/java/example/ServerOperations.java +++ /dev/null @@ -1,115 +0,0 @@ -package example; - -import java.io.IOException; -import java.util.List; - -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.Parameters; - -import ca.uhn.fhir.model.dstu2.composite.CodingDt; -import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.dstu2.resource.ConceptMap; -import ca.uhn.fhir.model.primitive.DateDt; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.TokenAndListParam; -import ca.uhn.fhir.rest.param.TokenParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -public class ServerOperations { - private static final Logger ourLog = LoggerFactory.getLogger(ServerOperations.class); - - - //START SNIPPET: manualInputAndOutput - @Operation(name="$manualInputAndOutput", manualResponse=true, manualRequest=true) - public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException { - String contentType = theServletRequest.getContentType(); - byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream()); - ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length); - - // In a real example we might do something more interesting with the received bytes, - // here we'll just replace them with hardcoded ones - bytes = new byte[] { 0, 1, 2, 3 }; - - theServletResponse.setContentType(contentType); - theServletResponse.getOutputStream().write(bytes); - theServletResponse.getOutputStream().close(); - } - //END SNIPPET: manualInputAndOutput - - - //START SNIPPET: searchParamBasic - @Operation(name="$find-matches", idempotent=true) - public Parameters findMatchesBasic( - @OperationParam(name="date") DateParam theDate, - @OperationParam(name="code") TokenParam theCode) { - - Parameters retVal = new Parameters(); - // Populate bundle with matching resources - return retVal; - } - //END SNIPPET: searchParamBasic - - //START SNIPPET: searchParamAdvanced - @Operation(name="$find-matches", idempotent=true) - public Parameters findMatchesAdvanced( - @OperationParam(name="dateRange") DateRangeParam theDate, - @OperationParam(name="name") List theName, - @OperationParam(name="code") TokenAndListParam theEnd) { - - Parameters retVal = new Parameters(); - // Populate bundle with matching resources - return retVal; - } - //END SNIPPET: searchParamAdvanced - - //START SNIPPET: patientTypeOperation - @Operation(name="$everything", idempotent=true) - public Bundle patientTypeOperation( - @OperationParam(name="start") DateDt theStart, - @OperationParam(name="end") DateDt theEnd) { - - Bundle retVal = new Bundle(); - // Populate bundle with matching resources - return retVal; - } - //END SNIPPET: patientTypeOperation - - //START SNIPPET: patientInstanceOperation - @Operation(name="$everything", idempotent=true) - public Bundle patientInstanceOperation( - @IdParam IdDt thePatientId, - @OperationParam(name="start") DateDt theStart, - @OperationParam(name="end") DateDt theEnd) { - - Bundle retVal = new Bundle(); - // Populate bundle with matching resources - return retVal; - } - //END SNIPPET: patientInstanceOperation - - //START SNIPPET: serverOperation - @Operation(name="$closure") - public ConceptMap closureOperation( - @OperationParam(name="name") StringDt theStart, - @OperationParam(name="concept") List theEnd, - @OperationParam(name="version") IdDt theVersion) { - - ConceptMap retVal = new ConceptMap(); - // Populate bundle with matching resources - return retVal; - } - //END SNIPPET: serverOperation - -} diff --git a/examples/src/main/java/example/ServletExamples.java b/examples/src/main/java/example/ServletExamples.java deleted file mode 100644 index 40fe909ed2f..00000000000 --- a/examples/src/main/java/example/ServletExamples.java +++ /dev/null @@ -1,155 +0,0 @@ -package example; - -import java.util.Arrays; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; - -import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; -import org.springframework.web.cors.CorsConfiguration; - -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.interceptor.*; -import ca.uhn.fhir.validation.ResultSeverityEnum; - -@SuppressWarnings("serial") -public class ServletExamples { - - // START SNIPPET: loggingInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class RestfulServerWithLogging extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - - // ... define your resource providers here ... - - // Now register the logging interceptor - LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); - registerInterceptor(loggingInterceptor); - - // The SLF4j logger "test.accesslog" will receive the logging events - loggingInterceptor.setLoggerName("test.accesslog"); - - // This is the format for each line. A number of substitution variables may - // be used here. See the JavaDoc for LoggingInterceptor for information on - // what is available. - loggingInterceptor.setMessageFormat("Source[${remoteAddr}] Operation[${operationType} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}]"); - - } - - } - // END SNIPPET: loggingInterceptor - - // START SNIPPET: validatingInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class ValidatingServerWithLogging extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - - // ... define your resource providers here ... - - // Create an interceptor to validate incoming requests - RequestValidatingInterceptor requestInterceptor = new RequestValidatingInterceptor(); - - // Register a validator module (you could also use SchemaBaseValidator and/or SchematronBaseValidator) - requestInterceptor.addValidatorModule(new FhirInstanceValidator()); - - requestInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); - requestInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestInterceptor.setResponseHeaderValue("Validation on ${line}: ${message} ${severity}"); - requestInterceptor.setResponseHeaderValueNoIssues("No issues detected"); - - // Now register the validating interceptor - registerInterceptor(requestInterceptor); - - // Create an interceptor to validate responses - // This is configured in the same way as above - ResponseValidatingInterceptor responseInterceptor = new ResponseValidatingInterceptor(); - responseInterceptor.addValidatorModule(new FhirInstanceValidator()); - responseInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); - responseInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - responseInterceptor.setResponseHeaderValue("Validation on ${line}: ${message} ${severity}"); - responseInterceptor.setResponseHeaderValueNoIssues("No issues detected"); - registerInterceptor(responseInterceptor); - } - - } - // END SNIPPET: validatingInterceptor - - // START SNIPPET: exceptionInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class RestfulServerWithExceptionHandling extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - - // ... define your resource providers here ... - - // Now register the interceptor - ExceptionHandlingInterceptor interceptor = new ExceptionHandlingInterceptor(); - registerInterceptor(interceptor); - - // Return the stack trace to the client for the following exception types - interceptor.setReturnStackTracesForExceptionTypes(InternalErrorException.class, NullPointerException.class); - - } - - } - // END SNIPPET: exceptionInterceptor - - // START SNIPPET: responseHighlighterInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class RestfulServerWithResponseHighlighter extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - - // ... define your resource providers here ... - - // Now register the interceptor - ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); - registerInterceptor(interceptor); - - } - - } - // END SNIPPET: responseHighlighterInterceptor - - // START SNIPPET: corsInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") - public class RestfulServerWithCors extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - - // ... define your resource providers here ... - - // Define your CORS configuration. This is an example - // showing a typical setup. You should customize this - // to your specific needs - CorsConfiguration config = new CorsConfiguration(); - config.addAllowedHeader("x-fhir-starter"); - config.addAllowedHeader("Origin"); - config.addAllowedHeader("Accept"); - config.addAllowedHeader("X-Requested-With"); - config.addAllowedHeader("Content-Type"); - - config.addAllowedOrigin("*"); - - config.addExposedHeader("Location"); - config.addExposedHeader("Content-Location"); - config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); - - // Create the interceptor and register it - CorsInterceptor interceptor = new CorsInterceptor(config); - registerInterceptor(interceptor); - - } - - } - // END SNIPPET: corsInterceptor - -} diff --git a/examples/src/main/java/example/TagsExamples.java b/examples/src/main/java/example/TagsExamples.java deleted file mode 100644 index 8a6c5a19aa5..00000000000 --- a/examples/src/main/java/example/TagsExamples.java +++ /dev/null @@ -1,69 +0,0 @@ -package example; - -import java.util.ArrayList; -import java.util.List; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class TagsExamples { - - public static void main(String[] args) { - new TagsExamples().getResourceTags(); - } - - @SuppressWarnings("unused") - public void getResourceTags() { - // START SNIPPET: getResourceTags - IGenericClient client = FhirContext.forDstu2().newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); - Patient p = client.read(Patient.class, "1"); - - // Retrieve the list of tags from the resource metadata - TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(p); - - // tags may be null if no tags were read in - if (tags == null) { - System.out.println("No tags!"); - } else { - - // You may iterate over all the tags - for (Tag next : tags) { - System.out.println(next.getScheme() + " - " + next.getTerm()); - } - - // You may also get a list of tags matching a given scheme - List someTags = tags.getTagsWithScheme("http://hl7.org/fhir/tag"); - - // Or a specific tag (by scheme and term) - Tag specificTag = tags.getTag("http://hl7.org/fhir/tag", "http://foo"); - - } - // END SNIPPET: getResourceTags - } - - // START SNIPPET: serverMethod - @Search - public List getAllPatients() { - ArrayList retVal = new ArrayList(); - - // Create a patient to return - Patient patient = new Patient(); - patient.setId("Patient/123"); - patient.addName().addFamily("Smith").addGiven("John"); - - // Create a tag list and add it to the resource - TagList tags = new TagList(); - ResourceMetadataKeyEnum.TAG_LIST.put(patient, tags); - - // Add some tags to the list - tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag1.html", "Some tag"); - tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag2.html", "Another tag"); - - return retVal; - } - // END SNIPPET: serverMethod - -} diff --git a/examples/src/main/java/example/ValidateDirectory.java b/examples/src/main/java/example/ValidateDirectory.java deleted file mode 100644 index 4c6ea0986ad..00000000000 --- a/examples/src/main/java/example/ValidateDirectory.java +++ /dev/null @@ -1,105 +0,0 @@ -package example; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.File; -import java.io.FileReader; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.validation.FhirValidator; -import ca.uhn.fhir.validation.ValidationResult; - -public class ValidateDirectory { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateDirectory.class); - - public static void main(String[] args) throws Exception { - // Load all profiles in this directory - File profileDirectory = new File("/tmp/directory/with/profiles"); - - // Validate resources in this directory - File resourceDirectory = new File("/tmp/directory/with/resources/to/validate"); - - FhirContext ctx = FhirContext.forDstu3(); - IParser xmlParser = ctx.newXmlParser(); - IParser jsonParser = ctx.newJsonParser(); - - Map structureDefinitions = new HashMap(); - Map codeSystems = new HashMap(); - Map valueSets = new HashMap(); - - // Load all profile files - for (File nextFile : profileDirectory.listFiles()) { - - IBaseResource parsedRes = null; - if (nextFile.getAbsolutePath().toLowerCase().endsWith(".xml")) { - parsedRes = xmlParser.parseResource(new FileReader(nextFile)); - } else if (nextFile.getAbsolutePath().toLowerCase().endsWith(".json")) { - parsedRes = jsonParser.parseResource(new FileReader(nextFile)); - } else { - ourLog.info("Ignoring file: {}", nextFile.getName()); - } - - if (parsedRes instanceof StructureDefinition) { - StructureDefinition res = (StructureDefinition) parsedRes; - if (isNotBlank(res.getUrl())) { - structureDefinitions.put(res.getUrl(), res); - } - } else if (parsedRes instanceof ValueSet) { - ValueSet res = (ValueSet) parsedRes; - if (isNotBlank(res.getUrl())) { - valueSets.put(res.getUrl(), res); - } - } else if (parsedRes instanceof CodeSystem) { - CodeSystem res = (CodeSystem) parsedRes; - if (isNotBlank(res.getUrl())) { - codeSystems.put(res.getUrl(), res); - } - } - } - - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); - - ValidationSupportChain validationSupportChain = new ValidationSupportChain(); - validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); - validationSupportChain.addValidationSupport(new PrePopulatedValidationSupport(structureDefinitions, valueSets, codeSystems)); - - instanceValidator.setValidationSupport(validationSupportChain); - - FhirValidator val = ctx.newValidator(); - val.registerValidatorModule(instanceValidator); - - // Loop through the files in the validation directory and validate each one - for (File nextFile : resourceDirectory.listFiles()) { - - if (nextFile.getAbsolutePath().toLowerCase().endsWith(".xml")) { - ourLog.info("Going to validate: {}", nextFile.getName()); - } else if (nextFile.getAbsolutePath().toLowerCase().endsWith(".json")) { - ourLog.info("Going to validate: {}", nextFile.getName()); - } else { - ourLog.info("Ignoring file: {}", nextFile.getName()); - continue; - } - - String input = IOUtils.toString(new FileReader(nextFile)); - ValidationResult result = val.validateWithResult(input); - IBaseOperationOutcome oo = result.toOperationOutcome(); - ourLog.info("Result:\n{}", xmlParser.setPrettyPrint(true).encodeResourceToString(oo)); - } - - } - -} diff --git a/examples/src/main/java/example/ValidateSimple.java b/examples/src/main/java/example/ValidateSimple.java deleted file mode 100644 index bef1e2b3912..00000000000 --- a/examples/src/main/java/example/ValidateSimple.java +++ /dev/null @@ -1,43 +0,0 @@ -package example; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.validation.FhirValidator; -import ca.uhn.fhir.validation.ValidationResult; -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; - -import java.io.FileReader; - -public class ValidateSimple { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateSimple.class); - - public static void main(String[] args) throws Exception { - FhirContext ctx = FhirContext.forR4(); - - // Create a validator module - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); - - // We'll create a validation chain with only the DefaultProfileValidationupport registered - ValidationSupportChain validationSupportChain = new ValidationSupportChain(); - validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); - instanceValidator.setValidationSupport(validationSupportChain); - - // Create a validator and register the InstanceValidator module - FhirValidator val = ctx.newValidator(); - val.registerValidatorModule(instanceValidator); - - // Read in the file and validate it - String nextFile = args[0]; - try (FileReader fileReader = new FileReader(nextFile)) { - String input = IOUtils.toString(fileReader); - ValidationResult result = val.validateWithResult(input); - IBaseOperationOutcome oo = result.toOperationOutcome(); - ourLog.info("Result:\n{}", ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); - } - - } - -} diff --git a/examples/src/main/java/example/ValidatorExamples.java b/examples/src/main/java/example/ValidatorExamples.java deleted file mode 100644 index 171b7088095..00000000000 --- a/examples/src/main/java/example/ValidatorExamples.java +++ /dev/null @@ -1,331 +0,0 @@ -package example; - -import java.io.File; -import java.io.FileReader; -import java.util.List; - -import javax.servlet.ServletException; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.filefilter.WildcardFileFilter; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.*; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.parser.StrictErrorHandler; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.validation.*; -import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; - -@SuppressWarnings("serial") -public class ValidatorExamples { - - public void validationIntro() { - // START SNIPPET: validationIntro - FhirContext ctx = FhirContext.forDstu3(); - - // Ask the context for a validator - FhirValidator validator = ctx.newValidator(); - - // Create some modules and register them - IValidatorModule module1 = new SchemaBaseValidator(ctx); - validator.registerValidatorModule(module1); - IValidatorModule module2 = new SchematronBaseValidator(ctx); - validator.registerValidatorModule(module2); - - // Pass a resource in to be validated. The resource can - // be an IBaseResource instance, or can be a raw String - // containing a serialized resource as text. - Patient resource = new Patient(); - ValidationResult result = validator.validateWithResult(resource); - String resourceText = ""; - ValidationResult result2 = validator.validateWithResult(resourceText); - - // The result object now contains the validation results - for (SingleValidationMessage next : result.getMessages()) { - System.out.println(next.getLocationString() + " " + next.getMessage()); - } - // END SNIPPET: validationIntro - } - - // START SNIPPET: serverValidation - public class MyRestfulServer extends RestfulServer { - - @Override - protected void initialize() throws ServletException { - // ...Configure resource providers, etc... - - // Create a context, set the error handler and instruct - // the server to use it - FhirContext ctx = FhirContext.forDstu3(); - ctx.setParserErrorHandler(new StrictErrorHandler()); - setFhirContext(ctx); - } - - } - // END SNIPPET: serverValidation - - @SuppressWarnings("unused") - public void enableValidation() { - // START SNIPPET: clientValidation - FhirContext ctx = FhirContext.forDstu3(); - - ctx.setParserErrorHandler(new StrictErrorHandler()); - - // This client will have strict parser validation enabled - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu3"); - // END SNIPPET: clientValidation - - } - - public void parserValidation() { - // START SNIPPET: parserValidation - FhirContext ctx = FhirContext.forDstu3(); - - // Create a parser and configure it to use the strict error handler - IParser parser = ctx.newXmlParser(); - parser.setParserErrorHandler(new StrictErrorHandler()); - - // This example resource is invalid, as Patient.active can not repeat - String input = ""; - - // The following will throw a DataFormatException because of the StrictErrorHandler - parser.parseResource(Patient.class, input); - // END SNIPPET: parserValidation - } - - public void validateResource() { - // START SNIPPET: basicValidation - // As always, you need a context - FhirContext ctx = FhirContext.forDstu3(); - - // Create and populate a new patient object - Patient p = new Patient(); - p.addName().setFamily("Smith").addGiven("John").addGiven("Q"); - p.addIdentifier().setSystem("urn:foo:identifiers").setValue("12345"); - p.addTelecom().setSystem(ContactPointSystem.PHONE).setValue("416 123-4567"); - - // Request a validator and apply it - FhirValidator val = ctx.newValidator(); - - // Create the Schema/Schematron modules and register them. Note that - // you might want to consider keeping these modules around as long-term - // objects: they parse and then store schemas, which can be an expensive - // operation. - IValidatorModule module1 = new SchemaBaseValidator(ctx); - IValidatorModule module2 = new SchematronBaseValidator(ctx); - val.registerValidatorModule(module1); - val.registerValidatorModule(module2); - - ValidationResult result = val.validateWithResult(p); - if (result.isSuccessful()) { - - System.out.println("Validation passed"); - - } else { - // We failed validation! - System.out.println("Validation failed"); - } - - // The result contains a list of "messages" - List messages = result.getMessages(); - for (SingleValidationMessage next : messages) { - System.out.println("Message:"); - System.out.println(" * Location: " + next.getLocationString()); - System.out.println(" * Severity: " + next.getSeverity()); - System.out.println(" * Message : " + next.getMessage()); - } - - // You can also convert the results into an OperationOutcome resource - OperationOutcome oo = (OperationOutcome) result.toOperationOutcome(); - String results = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo); - System.out.println(results); - // END SNIPPET: basicValidation - - } - - public static void main(String[] args) throws Exception { - instanceValidator(); - - } - - private static void instanceValidator() throws Exception { - // START SNIPPET: instanceValidator - FhirContext ctx = FhirContext.forDstu3(); - - // Create a FhirInstanceValidator and register it to a validator - FhirValidator validator = ctx.newValidator(); - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); - validator.registerValidatorModule(instanceValidator); - - /* - * If you want, you can configure settings on the validator to adjust - * its behaviour during validation - */ - instanceValidator.setAnyExtensionsAllowed(true); - - - /* - * Let's create a resource to validate. This Observation has some fields - * populated, but it is missing Observation.status, which is mandatory. - */ - Observation obs = new Observation(); - obs.getCode().addCoding().setSystem("http://loinc.org").setCode("12345-6"); - obs.setValue(new StringType("This is a value")); - - // Validate - ValidationResult result = validator.validateWithResult(obs); - - /* - * Note: You can also explicitly declare a profile to validate against - * using the block below. - */ - // ValidationResult result = validator.validateWithResult(obs, new ValidationOptions().addProfile("http://myprofile.com")); - - // Do we have any errors or fatal errors? - System.out.println(result.isSuccessful()); // false - - // Show the issues - for (SingleValidationMessage next : result.getMessages()) { - System.out.println(" Next issue " + next.getSeverity() + " - " + next.getLocationString() + " - " + next.getMessage()); - } - // Prints: - // Next issue ERROR - /f:Observation - Element '/f:Observation.status': minimum required = 1, but only found 0 - // Next issue WARNING - /f:Observation/f:code - Unable to validate code "12345-6" in code system "http://loinc.org" - - // You can also convert the result into an operation outcome if you - // need to return one from a server - OperationOutcome oo = (OperationOutcome) result.toOperationOutcome(); - // END SNIPPET: instanceValidator - } - - private static void instanceValidatorCustom() throws Exception { - // START SNIPPET: instanceValidatorCustom - FhirContext ctx = FhirContext.forDstu3(); - - // Create a FhirInstanceValidator and register it to a validator - FhirValidator validator = ctx.newValidator(); - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); - validator.registerValidatorModule(instanceValidator); - - IValidationSupport valSupport = new IValidationSupport() { - - @Override - public org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent theInclude) { - // TODO: implement - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - // TODO: implement - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - // TODO: implement - return null; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - // TODO: implement - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - // TODO: implement - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - // TODO: implement - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - // TODO: implement - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - // TODO: implement - return false; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) { - // TODO: implement - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - // TODO: implement - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - // TODO: implement - return null; - } - }; - - /* - * ValidationSupportChain strings multiple instances of IValidationSupport together. The - * code below is useful because it means that when the validator wants to load a - * StructureDefinition or a ValueSet, it will first use DefaultProfileValidationSupport, - * which loads the default HL7 versions. Any StructureDefinitions which are not found in - * the built-in set are delegated to your custom implementation. - */ - ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(), valSupport); - instanceValidator.setValidationSupport(support); - - // END SNIPPET: instanceValidatorCustom - } - - @SuppressWarnings("unused") - private static void validateFiles() throws Exception { - // START SNIPPET: validateFiles - FhirContext ctx = FhirContext.forDstu3(); - - // Create a validator and configure it - FhirValidator validator = ctx.newValidator(); - validator.setValidateAgainstStandardSchema(true); - validator.setValidateAgainstStandardSchematron(true); - - // Get a list of files in a given directory - String[] fileList = new File("/home/some/dir").list(new WildcardFileFilter("*.txt")); - for (String nextFile : fileList) { - - // For each file, load the contents into a string - String nextFileContents = IOUtils.toString(new FileReader(nextFile)); - - // Parse that string (this example assumes JSON encoding) - IBaseResource resource = ctx.newJsonParser().parseResource(nextFileContents); - - // Apply the validation. This will throw an exception on the first - // validation failure - ValidationResult result = validator.validateWithResult(resource); - if (result.isSuccessful() == false) { - throw new Exception("We failed!"); - } - - } - - // END SNIPPET: validateFiles - } - -} diff --git a/examples/src/main/java/example/ValidatorExamplesDstu3.java b/examples/src/main/java/example/ValidatorExamplesDstu3.java deleted file mode 100644 index 87a13cbdfbe..00000000000 --- a/examples/src/main/java/example/ValidatorExamplesDstu3.java +++ /dev/null @@ -1,49 +0,0 @@ -package example; - -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.validation.FhirValidator; - -public class ValidatorExamplesDstu3 { - - public void validateProfileDstu3() { - // START SNIPPET: validateFiles - - FhirContext ctx = FhirContext.forDstu3(); - FhirValidator validator = ctx.newValidator(); - - // Typically if you are doing profile validation, you want to disable - // the schema/schematron validation since the profile will specify - // all the same rules (and more) - validator.setValidateAgainstStandardSchema(false); - validator.setValidateAgainstStandardSchematron(false); - - // FhirInstanceValidator is the validation module that handles - // profile validation. So, create an InstanceValidator module - // and register it to the validator. - FhirInstanceValidator instanceVal = new FhirInstanceValidator(); - validator.registerValidatorModule(instanceVal); - - // FhirInstanceValidator requires an instance of "IValidationSupport" in - // order to function. This module is used by the validator to actually obtain - // all of the resources it needs in order to perform validation. Specifically, - // the validator uses it to fetch StructureDefinitions, ValueSets, CodeSystems, - // etc, as well as to perform terminology validation. - // - // The implementation used here (ValidationSupportChain) is allows for - // multiple implementations to be used in a chain, where if a specific resource - // is needed the whole chain is tried and the first module which is actually - // able to answer is used. The first entry in the chain that we register is - // the DefaultProfileValidationSupport, which supplies the "built-in" FHIR - // StructureDefinitions and ValueSets - ValidationSupportChain validationSupportChain = new ValidationSupportChain(); - validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); - instanceVal.setValidationSupport(validationSupportChain); - - // END SNIPPET: validateFiles - } - -} diff --git a/examples/src/main/java/example/customtype/CustomCompositeExtension.java b/examples/src/main/java/example/customtype/CustomCompositeExtension.java deleted file mode 100644 index 49a1ce5f5ce..00000000000 --- a/examples/src/main/java/example/customtype/CustomCompositeExtension.java +++ /dev/null @@ -1,84 +0,0 @@ -package example.customtype; - -import org.hl7.fhir.dstu3.model.BackboneElement; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.StringType; - -import ca.uhn.fhir.model.api.annotation.Block; -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.Extension; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.util.ElementUtil; - -//START SNIPPET: resource -@ResourceDef(name = "Patient") -public class CustomCompositeExtension extends Patient { - - private static final long serialVersionUID = 1L; - - /** - * A custom extension - */ - @Child(name = "foo") - @Extension(url="http://acme.org/fooParent", definedLocally = false, isModifier = false) - protected FooParentExtension fooParentExtension; - - public FooParentExtension getFooParentExtension() { - return fooParentExtension; - } - - @Override - public boolean isEmpty() { - return super.isEmpty() && ElementUtil.isEmpty(fooParentExtension); - } - - public void setFooParentExtension(FooParentExtension theFooParentExtension) { - fooParentExtension = theFooParentExtension; - } - - @Block - public static class FooParentExtension extends BackboneElement { - - private static final long serialVersionUID = 4522090347756045145L; - - @Child(name = "childA") - @Extension(url = "http://acme.org/fooChildA", definedLocally = false, isModifier = false) - private StringType myChildA; - - @Child(name = "childB") - @Extension(url = "http://acme.org/fooChildB", definedLocally = false, isModifier = false) - private StringType myChildB; - - @Override - public FooParentExtension copy() { - FooParentExtension copy = new FooParentExtension(); - copy.myChildA = myChildA; - copy.myChildB = myChildB; - return copy; - } - - @Override - public boolean isEmpty() { - return super.isEmpty() && ElementUtil.isEmpty(myChildA, myChildB); - } - - public StringType getChildA() { - return myChildA; - } - - public StringType getChildB() { - return myChildB; - } - - public void setChildA(StringType theChildA) { - myChildA = theChildA; - } - - public void setChildB(StringType theChildB) { - myChildB = theChildB; - } - - } - -} -//END SNIPPET: resource diff --git a/examples/src/main/java/example/customtype/CustomDatatype.java b/examples/src/main/java/example/customtype/CustomDatatype.java deleted file mode 100644 index fc3ecbc0720..00000000000 --- a/examples/src/main/java/example/customtype/CustomDatatype.java +++ /dev/null @@ -1,63 +0,0 @@ -package example.customtype; - -//START SNIPPET: datatype -import org.hl7.fhir.dstu3.model.DateTimeType; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.Type; -import org.hl7.fhir.instance.model.api.ICompositeType; - -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.DatatypeDef; -import ca.uhn.fhir.util.ElementUtil; - -/** - * This is an example of a custom datatype. - * - * This is an STU3 example so it extends Type and implements ICompositeType. For - * DSTU2 it would extend BaseIdentifiableElement and implement ICompositeDatatype. - */ -@DatatypeDef(name="CustomDatatype") -public class CustomDatatype extends Type implements ICompositeType { - - private static final long serialVersionUID = 1L; - - @Child(name = "date", order = 0, min = 1, max = 1) - private DateTimeType myDate; - - @Child(name = "kittens", order = 1, min = 1, max = 1) - private StringType myKittens; - - public DateTimeType getDate() { - if (myDate == null) - myDate = new DateTimeType(); - return myDate; - } - - public StringType getKittens() { - return myKittens; - } - - @Override - public boolean isEmpty() { - return ElementUtil.isEmpty(myDate, myKittens); - } - - public CustomDatatype setDate(DateTimeType theValue) { - myDate = theValue; - return this; - } - - public CustomDatatype setKittens(StringType theKittens) { - myKittens = theKittens; - return this; - } - - @Override - protected CustomDatatype typedCopy() { - CustomDatatype retVal = new CustomDatatype(); - super.copyValues(retVal); - retVal.myDate = myDate; - return retVal; - } -} -//END SNIPPET: datatype \ No newline at end of file diff --git a/examples/src/main/java/example/customtype/CustomResource.java b/examples/src/main/java/example/customtype/CustomResource.java deleted file mode 100644 index 3e1862c5b4a..00000000000 --- a/examples/src/main/java/example/customtype/CustomResource.java +++ /dev/null @@ -1,86 +0,0 @@ -package example.customtype; - -// START SNIPPET: resource -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.dstu3.model.DomainResource; -import org.hl7.fhir.dstu3.model.ResourceType; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.Type; - -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.util.ElementUtil; - -/** - * This is an example of a custom resource that also uses a custom - * datatype. - * - * Note that we are extensing DomainResource for an STU3 - * resource. For DSTU2 it would be BaseResource. - */ -@ResourceDef(name = "CustomResource", profile = "http://hl7.org/fhir/profiles/custom-resource") -public class CustomResource extends DomainResource { - - private static final long serialVersionUID = 1L; - - /** - * We give the resource a field with name "television". This field has no - * specific type, so it's a choice[x] field for any type. - */ - @Child(name="television", min=1, max=Child.MAX_UNLIMITED, order=0) - private List myTelevision; - - /** - * We'll give it one more field called "dogs" - */ - @Child(name = "dogs", min=0, max=1, order=1) - private StringType myDogs; - - @Override - public CustomResource copy() { - CustomResource retVal = new CustomResource(); - super.copyValues(retVal); - retVal.myTelevision = myTelevision; - retVal.myDogs = myDogs; - return retVal; - } - - public List getTelevision() { - if (myTelevision == null) { - myTelevision = new ArrayList(); - } - return myTelevision; - } - - public StringType getDogs() { - return myDogs; - } - - @Override - public ResourceType getResourceType() { - return null; - } - - @Override - public FhirVersionEnum getStructureFhirVersionEnum() { - return FhirVersionEnum.DSTU3; - } - - @Override - public boolean isEmpty() { - return ElementUtil.isEmpty(myTelevision, myDogs); - } - - public void setTelevision(List theValue) { - this.myTelevision = theValue; - } - - public void setDogs(StringType theDogs) { - myDogs = theDogs; - } - -} -// END SNIPPET: resource diff --git a/examples/src/main/java/example/customtype/CustomUsage.java b/examples/src/main/java/example/customtype/CustomUsage.java deleted file mode 100644 index a823aea607f..00000000000 --- a/examples/src/main/java/example/customtype/CustomUsage.java +++ /dev/null @@ -1,44 +0,0 @@ -package example.customtype; - -import java.util.Date; - -import org.hl7.fhir.dstu3.model.DateTimeType; -import org.hl7.fhir.dstu3.model.DateType; -import org.hl7.fhir.dstu3.model.StringType; - -import ca.uhn.fhir.context.FhirContext; - -public class CustomUsage { - - public static void main(String[] args) { - - // START SNIPPET: usage - // Create a context. Note that we declare the custom types we'll be using - // on the context before actually using them - FhirContext ctx = FhirContext.forDstu3(); - ctx.registerCustomType(CustomResource.class); - ctx.registerCustomType(CustomDatatype.class); - - // Now let's create an instance of our custom resource type - // and populate it with some data - CustomResource res = new CustomResource(); - - // Add some values, including our custom datatype - DateType value0 = new DateType("2015-01-01"); - res.getTelevision().add(value0); - - CustomDatatype value1 = new CustomDatatype(); - value1.setDate(new DateTimeType(new Date())); - value1.setKittens(new StringType("FOO")); - res.getTelevision().add(value1); - - res.setDogs(new StringType("Some Dogs")); - - // Now let's serialize our instance - String output = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res); - System.out.println(output); - // END SNIPPET: usage - - } - -} diff --git a/examples/src/main/java/example/interceptor/MyTestInterceptor.java b/examples/src/main/java/example/interceptor/MyTestInterceptor.java deleted file mode 100644 index bd62ea36b64..00000000000 --- a/examples/src/main/java/example/interceptor/MyTestInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package example.interceptor; - -import ca.uhn.fhir.interceptor.api.Hook; -import ca.uhn.fhir.interceptor.api.Interceptor; -import ca.uhn.fhir.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; - } - -} diff --git a/examples/src/main/java/logback.xml b/examples/src/main/java/logback.xml deleted file mode 100644 index 1c8de9da344..00000000000 --- a/examples/src/main/java/logback.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - INFO - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n - - - - - - - - \ No newline at end of file diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index cfbbacddefa..599b8fe0ee4 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml @@ -181,7 +181,16 @@ - http://jamesagnew.github.io/hapi-fhir/apidocs/ + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-base + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-dstu2 + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-dstu3 + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-r4 + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-r5 + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-client + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-server + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-jpaserver-base + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-converter + https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-structures-validation https://docs.oracle.com/javaee/7/api/ -Xdoclint:none diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 129477c4a80..363a68022b2 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index eb562662816..6e01dce40bc 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -20,8 +20,8 @@ - com.google.code.gson - gson + com.fasterxml.jackson.core + jackson-databind diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java index 90e4a50ac91..fbe2fc502dc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java @@ -22,8 +22,11 @@ package ca.uhn.fhir.context; import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.util.*; @@ -33,11 +36,11 @@ public abstract class BaseRuntimeElementDefinition { private final Class myImplementingClass; private final String myName; private final boolean myStandardType; - private Map, Constructor> myConstructors = Collections.synchronizedMap(new HashMap, Constructor>()); - private List myExtensions = new ArrayList(); - private List myExtensionsModifier = new ArrayList(); - private List myExtensionsNonModifier = new ArrayList(); - private Map myUrlToExtension = new HashMap(); + private Map, Constructor> myConstructors = Collections.synchronizedMap(new HashMap<>()); + private List myExtensions = new ArrayList<>(); + private List myExtensionsModifier = new ArrayList<>(); + private List myExtensionsNonModifier = new ArrayList<>(); + private Map myUrlToExtension = new HashMap<>(); private BaseRuntimeElementDefinition myRootParentDefinition; public BaseRuntimeElementDefinition(String theName, Class theImplementingClass, boolean theStandardType) { @@ -56,19 +59,17 @@ public abstract class BaseRuntimeElementDefinition { myImplementingClass = theImplementingClass; } - public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) { - if (theExtension == null) { - throw new NullPointerException(); - } + public void addExtension(@Nonnull RuntimeChildDeclaredExtensionDefinition theExtension) { + Validate.notNull(theExtension, "theExtension must not be null"); myExtensions.add(theExtension); } public abstract ChildTypeEnum getChildType(); @SuppressWarnings("unchecked") - private Constructor getConstructor(Object theArgument) { + private Constructor getConstructor(@Nullable Object theArgument) { - Class argumentType; + Class argumentType; if (theArgument == null) { argumentType = VOID_CLASS; } else { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index c938e30761c..7458be5edca 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -1,15 +1,22 @@ package ca.uhn.fhir.context; import ca.uhn.fhir.context.api.AddProfileTagEnum; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.view.ViewGenerator; import ca.uhn.fhir.narrative.INarrativeGenerator; -import ca.uhn.fhir.parser.*; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.parser.IParserErrorHandler; +import ca.uhn.fhir.parser.JsonParser; +import ca.uhn.fhir.parser.LenientErrorHandler; +import ca.uhn.fhir.parser.RDFParser; +import ca.uhn.fhir.parser.XmlParser; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IGenericClient; @@ -20,7 +27,6 @@ import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.validation.FhirValidator; import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.jena.riot.Lang; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -97,7 +103,7 @@ public class FhirContext { private Collection> myResourceTypesToScan; private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; - private IContextValidationSupport myValidationSupport; + private IValidationSupport myValidationSupport; private Map>> myVersionToNameToResourceType = Collections.emptyMap(); /** @@ -371,15 +377,27 @@ public class FhirContext { } } + /** + * Sets the configured performance options + * + * @see PerformanceOptionsEnum for a list of available options + */ + public void setPerformanceOptions(final PerformanceOptionsEnum... thePerformanceOptions) { + Collection asList = null; + if (thePerformanceOptions != null) { + asList = Arrays.asList(thePerformanceOptions); + } + setPerformanceOptions(asList); + } + /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinition(final Class theResourceType) { validateInitialized(); - if (theResourceType == null) { - throw new NullPointerException("theResourceType can not be null"); - } + Validate.notNull(theResourceType, "theResourceType can not be null"); + if (Modifier.isAbstract(theResourceType.getModifiers())) { throw new IllegalArgumentException("Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName()); } @@ -544,16 +562,37 @@ public class FhirContext { /** * Returns the validation support module configured for this context, creating a default - * implementation if no module has been passed in via the {@link #setValidationSupport(IContextValidationSupport)} + * implementation if no module has been passed in via the {@link #setValidationSupport(IValidationSupport)} * method * - * @see #setValidationSupport(IContextValidationSupport) + * @see #setValidationSupport(IValidationSupport) */ - public IContextValidationSupport getValidationSupport() { - if (myValidationSupport == null) { - myValidationSupport = myVersion.createValidationSupport(); + public IValidationSupport getValidationSupport() { + IValidationSupport retVal = myValidationSupport; + if (retVal == null) { + retVal = new DefaultProfileValidationSupport(this); + + /* + * If hapi-fhir-validation is on the classpath, we can create a much more robust + * validation chain using the classes found in that package + */ + String inMemoryTermSvcType = "org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport"; + String commonCodeSystemsSupportType = "org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService"; + if (ReflectionUtil.typeExists(inMemoryTermSvcType)) { + IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull(inMemoryTermSvcType, IValidationSupport.class, new Class[]{FhirContext.class}, new Object[]{this}); + IValidationSupport commonCodeSystemsSupport = ReflectionUtil.newInstanceOrReturnNull(commonCodeSystemsSupportType, IValidationSupport.class, new Class[]{FhirContext.class}, new Object[]{this}); + retVal = ReflectionUtil.newInstanceOrReturnNull("org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain", IValidationSupport.class, new Class[]{IValidationSupport[].class}, new Object[]{new IValidationSupport[]{ + retVal, + inMemoryTermSvc, + commonCodeSystemsSupport + }}); + assert retVal != null : "Failed to instantiate " + "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain"; + } + + + myValidationSupport = retVal; } - return myValidationSupport; + return retVal; } /** @@ -561,7 +600,7 @@ public class FhirContext { * is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc) * as well as to provide terminology services to modules such as the validator and FluentPath executor */ - public void setValidationSupport(IContextValidationSupport theValidationSupport) { + public void setValidationSupport(IValidationSupport theValidationSupport) { myValidationSupport = theValidationSupport; } @@ -586,12 +625,21 @@ public class FhirContext { } /** - * Creates a new FluentPath engine which can be used to exvaluate + * @since 2.2 + * @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead. + */ + @Deprecated + public IFhirPath newFluentPath() { + return newFhirPath(); + } + + /** + * Creates a new FhirPath engine which can be used to evaluate * path expressions over FHIR resources. Note that this engine will use the - * {@link IContextValidationSupport context validation support} module which is + * {@link IValidationSupport context validation support} module which is * configured on the context at the time this method is called. *

- * In other words, call {@link #setValidationSupport(IContextValidationSupport)} before + * In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before * calling {@link #newFluentPath()} *

*

@@ -601,10 +649,10 @@ public class FhirContext { * {@link UnsupportedOperationException} *

* - * @since 2.2 + * @since 5.0.0 */ - public IFluentPath newFluentPath() { - return myVersion.createFluentPathExecutor(this); + public IFhirPath newFhirPath() { + return myVersion.createFhirPathExecutor(this); } /** @@ -642,13 +690,12 @@ public class FhirContext { return new RDFParser(this, myParserErrorHandler, Lang.TURTLE); } - /** * Instantiates a new client instance. This method requires an interface which is defined specifically for your use * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy", * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its * sub-interface {@link IBasicClient}). See the RESTful Client documentation for more + * href="https://hapifhir.io/hapi-fhir/docs/client/introduction.html">RESTful Client documentation for more * information on how to define this interface. * *

@@ -862,19 +909,6 @@ public class FhirContext { myParserErrorHandler = theParserErrorHandler; } - /** - * Sets the configured performance options - * - * @see PerformanceOptionsEnum for a list of available options - */ - public void setPerformanceOptions(final PerformanceOptionsEnum... thePerformanceOptions) { - Collection asList = null; - if (thePerformanceOptions != null) { - asList = Arrays.asList(thePerformanceOptions); - } - setPerformanceOptions(asList); - } - @SuppressWarnings({"cast"}) private List> toElementList(final Collection> theResourceTypes) { if (theResourceTypes == null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java new file mode 100644 index 00000000000..7d1d3f2d135 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ConceptValidationOptions.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.context.support; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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 class ConceptValidationOptions { + + public boolean isInferSystem() { + return myInferSystem; + } + + public ConceptValidationOptions setInferSystem(boolean theInferSystem) { + myInferSystem = theInferSystem; + return this; + } + + private boolean myInferSystem; + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java new file mode 100644 index 00000000000..3498ac29df0 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java @@ -0,0 +1,354 @@ +package ca.uhn.fhir.context.support; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.util.BundleUtil; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class DefaultProfileValidationSupport implements IValidationSupport { + + private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/"; + private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/"; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class); + private final FhirContext myCtx; + + private Map myCodeSystems; + private Map myStructureDefinitions; + private Map myValueSets; + private List myTerminologyResources; + private List myStructureDefinitionResources; + + /** + * Constructor + * + * @param theFhirContext The context to use + */ + public DefaultProfileValidationSupport(FhirContext theFhirContext) { + myCtx = theFhirContext; + } + + + private void initializeResourceLists() { + + if (myTerminologyResources != null && myStructureDefinitionResources != null) { + return; + } + + List terminologyResources = new ArrayList<>(); + List structureDefinitionResources = new ArrayList<>(); + switch (getFhirContext().getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + terminologyResources.add("/org/hl7/fhir/instance/model/valueset/valuesets.xml"); + terminologyResources.add("/org/hl7/fhir/instance/model/valueset/v2-tables.xml"); + terminologyResources.add("/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml"); + Properties profileNameProperties = new Properties(); + try { + profileNameProperties.load(DefaultProfileValidationSupport.class.getResourceAsStream("/org/hl7/fhir/instance/model/profile/profiles.properties")); + for (Object nextKey : profileNameProperties.keySet()) { + structureDefinitionResources.add("/org/hl7/fhir/instance/model/profile/" + nextKey); + } + } catch (IOException e) { + throw new ConfigurationException(e); + } + break; + case DSTU2_1: + terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/valuesets.xml"); + terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/v2-tables.xml"); + terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/v3-codesystems.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-resources.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-types.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-others.xml"); + break; + case DSTU3: + terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/valuesets.xml"); + terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/v2-tables.xml"); + terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/v3-codesystems.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-resources.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-types.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-others.xml"); + structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/extension/extension-definitions.xml"); + break; + case R4: + terminologyResources.add("/org/hl7/fhir/r4/model/valueset/valuesets.xml"); + terminologyResources.add("/org/hl7/fhir/r4/model/valueset/v2-tables.xml"); + terminologyResources.add("/org/hl7/fhir/r4/model/valueset/v3-codesystems.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-resources.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-types.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-others.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r4/model/extension/extension-definitions.xml"); + break; + case R5: + structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-resources.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-types.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-others.xml"); + structureDefinitionResources.add("/org/hl7/fhir/r5/model/extension/extension-definitions.xml"); + terminologyResources.add("/org/hl7/fhir/r5/model/valueset/valuesets.xml"); + terminologyResources.add("/org/hl7/fhir/r5/model/valueset/v2-tables.xml"); + terminologyResources.add("/org/hl7/fhir/r5/model/valueset/v3-codesystems.xml"); + break; + } + + myTerminologyResources = terminologyResources; + myStructureDefinitionResources = structureDefinitionResources; + } + + + @Override + public List fetchAllConformanceResources() { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + + @Override + public List fetchAllStructureDefinitions() { + return toList(provideStructureDefinitionMap()); + } + + + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + return fetchCodeSystemOrValueSet(theSystem, true); + } + + private IBaseResource fetchCodeSystemOrValueSet(String theSystem, boolean codeSystem) { + synchronized (this) { + Map codeSystems = myCodeSystems; + Map valueSets = myValueSets; + if (codeSystems == null || valueSets == null) { + codeSystems = new HashMap<>(); + valueSets = new HashMap<>(); + + initializeResourceLists(); + for (String next : myTerminologyResources) { + loadCodeSystems(codeSystems, valueSets, next); + } + + myCodeSystems = codeSystems; + myValueSets = valueSets; + } + + // System can take the form "http://url|version" + String system = theSystem; + if (system.contains("|")) { + String version = system.substring(system.indexOf('|') + 1); + if (version.matches("^[0-9.]+$")) { + system = system.substring(0, system.indexOf('|')); + } + } + + if (codeSystem) { + return codeSystems.get(system); + } else { + return valueSets.get(system); + } + } + } + + @Override + public IBaseResource fetchStructureDefinition(String theUrl) { + String url = theUrl; + if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { + // no change + } else if (url.indexOf('/') == -1) { + url = URL_PREFIX_STRUCTURE_DEFINITION + url; + } else if (StringUtils.countMatches(url, '/') == 1) { + url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; + } + Map structureDefinitionMap = provideStructureDefinitionMap(); + IBaseResource retVal = structureDefinitionMap.get(url); + return retVal; + } + + @Override + public IBaseResource fetchValueSet(String theUrl) { + IBaseResource retVal = fetchCodeSystemOrValueSet(theUrl, false); + return retVal; + } + + public void flush() { + myCodeSystems = null; + myStructureDefinitions = null; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } + + private Map provideStructureDefinitionMap() { + Map structureDefinitions = myStructureDefinitions; + if (structureDefinitions == null) { + structureDefinitions = new HashMap<>(); + + initializeResourceLists(); + for (String next : myStructureDefinitionResources) { + loadStructureDefinitions(structureDefinitions, next); + } + + myStructureDefinitions = structureDefinitions; + } + return structureDefinitions; + } + + private void loadCodeSystems(Map theCodeSystems, Map theValueSets, String theClasspath) { + ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); + InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); + InputStreamReader reader = null; + if (inputStream != null) { + try { + reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8); + List resources = parseBundle(reader); + for (IBaseResource next : resources) { + + RuntimeResourceDefinition nextDef = getFhirContext().getResourceDefinition(next); + Map map = null; + switch (nextDef.getName()) { + case "CodeSystem": + map = theCodeSystems; + break; + case "ValueSet": + map = theValueSets; + break; + } + + if (map != null) { + String urlValueString = getConformanceResourceUrl(next); + if (isNotBlank(urlValueString)) { + map.put(urlValueString, next); + } + + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + + IPrimitiveType codeSystem = myCtx.newTerser().getSingleValueOrNull(next, "ValueSet.codeSystem.system", IPrimitiveType.class); + if (codeSystem != null && isNotBlank(codeSystem.getValueAsString())) { + theCodeSystems.put(codeSystem.getValueAsString(), next); + } + + break; + + default: + case DSTU2_1: + case DSTU3: + case R4: + case R5: + break; + } + } + + + } + } finally { + try { + if (reader != null) { + reader.close(); + } + inputStream.close(); + } catch (IOException e) { + ourLog.warn("Failure closing stream", e); + } + } + } else { + ourLog.warn("Unable to load resource: {}", theClasspath); + } + } + + private void loadStructureDefinitions(Map theCodeSystems, String theClasspath) { + ourLog.info("Loading structure definitions from classpath: {}", theClasspath); + try (InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath)) { + if (valuesetText != null) { + try (InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8)) { + + List resources = parseBundle(reader); + for (IBaseResource next : resources) { + + String nextType = getFhirContext().getResourceDefinition(next).getName(); + if ("StructureDefinition".equals(nextType)) { + + String url = getConformanceResourceUrl(next); + if (isNotBlank(url)) { + theCodeSystems.put(url, next); + } + + } + + } + } + } else { + ourLog.warn("Unable to load resource: {}", theClasspath); + } + } catch (IOException theE) { + ourLog.warn("Unable to load resource: {}", theClasspath); + } + } + + private String getConformanceResourceUrl(IBaseResource theResource) { + String urlValueString = null; + Optional urlValue = getFhirContext().getResourceDefinition(theResource).getChildByName("url").getAccessor().getFirstValueOrNull(theResource); + if (urlValue.isPresent()) { + IPrimitiveType urlValueType = (IPrimitiveType) urlValue.get(); + urlValueString = urlValueType.getValueAsString(); + } + return urlValueString; + } + + private List parseBundle(InputStreamReader theReader) { + IBaseResource parsedObject = getFhirContext().newXmlParser().parseResource(theReader); + if (parsedObject instanceof IBaseBundle) { + IBaseBundle bundle = (IBaseBundle) parsedObject; + return BundleUtil.toListOfResources(getFhirContext(), bundle); + } else { + return Collections.singletonList(parsedObject); + } + } + + static List toList(Map theMap) { + ArrayList retVal = new ArrayList<>(theMap.values()); + return (List) Collections.unmodifiableList(retVal); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java similarity index 50% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index d0fb1bca555..fc5089e2163 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -23,12 +23,14 @@ package ca.uhn.fhir.context.support; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.ParametersUtil; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -42,33 +44,63 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * various functions that can be provided by validation and terminology * services. *

+ * This interface is invoked directly by internal parts of the HAPI FHIR API, including the + * Validator and the FHIRPath evaluator. It is used to supply artifacts required for validation + * (e.g. StructureDefinition resources, ValueSet resources, etc.) and also to provide + * terminology functions such as code validation, ValueSet expansion, etc. + *

+ *

* Implementations are not required to implement all of the functions * in this interface; in fact it is expected that most won't. Any * methods which are not implemented may simply return null - * and calling code is expected to be able to handle this. + * and calling code is expected to be able to handle this. Generally, a + * series of implementations of this interface will be joined together using + * the + * ValidationSupportChain + * class. *

+ *

+ * See Validation Support Modules + * for information on how to assemble and configure implementations of this interface. See also + * the org.hl7.fhir.common.hapi.validation.support + * package summary + * in the hapi-fhir-validation module for many implementations of this interface. + *

+ * + * @since 5.0.0 */ -public interface IContextValidationSupport { +public interface IValidationSupport { + String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; + /** * Expands the given portion of a ValueSet * - * @param theInclude The portion to include - * @return The expansion + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theExpansionOptions If provided (may be null), contains options controlling the expansion + * @param theValueSetToExpand The valueset that should be expanded + * @return The expansion, or null */ - EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude); + default ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, @Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) { + return null; + } /** * Load and return all conformance resources associated with this * validation support module. This method may return null if it doesn't * make sense for a given module. */ - List fetchAllConformanceResources(FhirContext theContext); + default List fetchAllConformanceResources() { + return null; + } /** * Load and return all possible structure definitions */ - List fetchAllStructureDefinitions(FhirContext theContext); + default List fetchAllStructureDefinitions() { + return null; + } /** * Fetch a code system by ID @@ -76,70 +108,146 @@ public interface IContextValidationSupport * @param theSystem The code system * @return The valueset (must not be null, but can be an empty ValueSet) */ - CST fetchCodeSystem(FhirContext theContext, String theSystem); + default IBaseResource fetchCodeSystem(String theSystem) { + return null; + } /** * Loads a resource needed by the validation (a StructureDefinition, or a * ValueSet) * - * @param theContext The HAPI FHIR Context object current in use by the validator - * @param theClass The type of the resource to load - * @param theUri The resource URI + * @param theClass The type of the resource to load + * @param theUri The resource URI * @return Returns the resource, or null if no resource with the * given URI can be found */ - T fetchResource(FhirContext theContext, Class theClass, String theUri); + default T fetchResource(Class theClass, String theUri) { + Validate.notNull(theClass, "theClass must not be null or blank"); + Validate.notBlank(theUri, "theUri must not be null or blank"); - SDT fetchStructureDefinition(FhirContext theCtx, String theUrl); + switch (getFhirContext().getResourceDefinition(theClass).getName()) { + case "StructureDefinition": + return theClass.cast(fetchStructureDefinition(theUri)); + case "ValueSet": + return theClass.cast(fetchValueSet(theUri)); + case "CodeSystem": + return theClass.cast(fetchCodeSystem(theUri)); + } + + if (theUri.startsWith(URL_PREFIX_VALUE_SET)) { + return theClass.cast(fetchValueSet(theUri)); + } + + return null; + } + + default IBaseResource fetchStructureDefinition(String theUrl) { + return null; + } /** * Returns true if codes in the given code system can be expanded * or validated * - * @param theSystem The URI for the code system, e.g. "http://loinc.org" + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theSystem The URI for the code system, e.g. "http://loinc.org" * @return Returns true if codes in the given code system can be * validated */ - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); + default boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + return false; + } /** * Fetch the given ValueSet by URL */ - IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl); + default IBaseResource fetchValueSet(String theValueSetUrl) { + return null; + } /** * Validates that the given code exists and if possible returns a display * name. This method is called to check codes which are found in "example" * binding fields (e.g. Observation.code in the default profile. * - * @param theCodeSystem The code system, e.g. "http://loinc.org" - * @param theCode The code, e.g. "1234-5" - * @param theDisplay The display name, if it should also be validated + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theOptions Provides options controlling the validation + * @param theCodeSystem The code system, e.g. "http://loinc.org" + * @param theCode The code, e.g. "1234-5" + * @param theDisplay The display name, if it should also be validated * @return Returns a validation result object */ - CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl); + default CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + return null; + } /** * Validates that the given code exists and if possible returns a display * name. This method is called to check codes which are found in "example" * binding fields (e.g. Observation.code in the default profile. * - * @param theCodeSystem The code system, e.g. "http://loinc.org" - * @param theCode The code, e.g. "1234-5" - * @param theDisplay The display name, if it should also be validated - * @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource. + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theCodeSystem The code system, e.g. "http://loinc.org" + * @param theCode The code, e.g. "1234-5" + * @param theDisplay The display name, if it should also be validated + * @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource. * @return Returns a validation result object, or null if this validation support module can not handle this kind of request */ - default CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { return null; } + default CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + return null; + } /** * Look up a code using the system and code value * - * @param theContext The FHIR context - * @param theSystem The CodeSystem URL - * @param theCode The code + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theSystem The CodeSystem URL + * @param theCode The code */ - LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode); + default LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return null; + } + + /** + * Returns true if the given valueset can be validated by the given + * validation support module + * + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theValueSetUrl The ValueSet canonical URL + */ + default boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + return false; + } + + /** + * Generate a snapshot from the given differential profile. + * + * @param theRootValidationSupport The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @return Returns null if this module does not know how to handle this request + */ + default IBaseResource generateSnapshot(IValidationSupport theRootValidationSupport, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) { + return null; + } + + /** + * Returns the FHIR Context associated with this module + */ + FhirContext getFhirContext(); + + /** + * This method clears any temporary caches within the validation support. It is mainly intended for unit tests, + * but could be used in non-test scenarios as well. + */ + default void invalidateCaches() { + // nothing + } + class ConceptDesignation { private String myLanguage; @@ -257,59 +365,83 @@ public interface IContextValidationSupport } } + enum IssueSeverity { + /** + * The issue caused the action to fail, and no further checking could be performed. + */ + FATAL, + /** + * The issue is sufficiently important to cause the action to fail. + */ + ERROR, + /** + * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired. + */ + WARNING, + /** + * The issue has no relation to the degree of success of the action. + */ + INFORMATION + } + class CodeValidationResult { - private IBase myDefinition; + private String myCode; private String myMessage; - private Enum mySeverity; + private IssueSeverity mySeverity; private String myCodeSystemName; private String myCodeSystemVersion; private List myProperties; private String myDisplay; - public CodeValidationResult(IBase theDefinition) { - this.myDefinition = theDefinition; - } - - public CodeValidationResult(Enum theSeverity, String message) { - this.mySeverity = theSeverity; - this.myMessage = message; - } - - public CodeValidationResult(Enum theSeverity, String theMessage, IBase theDefinition, String theDisplay) { - this.mySeverity = theSeverity; - this.myMessage = theMessage; - this.myDefinition = theDefinition; - this.myDisplay = theDisplay; + public CodeValidationResult() { + super(); } public String getDisplay() { return myDisplay; } - public IBase asConceptDefinition() { - return myDefinition; + public CodeValidationResult setDisplay(String theDisplay) { + myDisplay = theDisplay; + return this; + } + + public String getCode() { + return myCode; + } + + public CodeValidationResult setCode(String theCode) { + myCode = theCode; + return this; } String getCodeSystemName() { return myCodeSystemName; } - public void setCodeSystemName(String theCodeSystemName) { + public CodeValidationResult setCodeSystemName(String theCodeSystemName) { myCodeSystemName = theCodeSystemName; + return this; } public String getCodeSystemVersion() { return myCodeSystemVersion; } - public void setCodeSystemVersion(String theCodeSystemVersion) { + public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) { myCodeSystemVersion = theCodeSystemVersion; + return this; } public String getMessage() { return myMessage; } + public CodeValidationResult setMessage(String theMessage) { + myMessage = theMessage; + return this; + } + public List getProperties() { return myProperties; } @@ -318,12 +450,17 @@ public interface IContextValidationSupport myProperties = theProperties; } - public Enum getSeverity() { + public IssueSeverity getSeverity() { return mySeverity; } + public CodeValidationResult setSeverity(IssueSeverity theSeverity) { + mySeverity = theSeverity; + return this; + } + public boolean isOk() { - return myDefinition != null; + return isNotBlank(myCode); } public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) { @@ -339,6 +476,49 @@ public interface IContextValidationSupport return retVal; } + /** + * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string + */ + public String getSeverityCode() { + String retVal = null; + if (getSeverity() != null) { + retVal = getSeverity().name().toLowerCase(); + } + return retVal; + } + + /** + * Sets an issue severity as a string code. Value must be the name of + * one of the enum values in {@link IssueSeverity}. Value is case-insensitive. + */ + public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) { + setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase())); + return this; + } + } + + class ValueSetExpansionOutcome { + + private final IBaseResource myValueSet; + private final String myError; + + public ValueSetExpansionOutcome(IBaseResource theValueSet, String theError) { + myValueSet = theValueSet; + myError = theError; + } + + public ValueSetExpansionOutcome(IBaseResource theValueSet) { + myValueSet = theValueSet; + myError = null; + } + + public String getError() { + return myError; + } + + public IBaseResource getValueSet() { + return myValueSet; + } } class LookupCodeResult { @@ -350,7 +530,7 @@ public interface IContextValidationSupport private boolean myFound; private String mySearchedForCode; private String mySearchedForSystem; - private List myProperties; + private List myProperties; private List myDesignations; /** @@ -367,7 +547,7 @@ public interface IContextValidationSupport return myProperties; } - public void setProperties(List theProperties) { + public void setProperties(List theProperties) { myProperties = theProperties; } @@ -466,7 +646,7 @@ public interface IContextValidationSupport .collect(Collectors.toSet()); } - for (IContextValidationSupport.BaseConceptProperty next : myProperties) { + for (IValidationSupport.BaseConceptProperty next : myProperties) { if (!properties.isEmpty()) { if (!properties.contains(next.getPropertyName())) { @@ -477,11 +657,11 @@ public interface IContextValidationSupport IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName()); - if (next instanceof IContextValidationSupport.StringConceptProperty) { - IContextValidationSupport.StringConceptProperty prop = (IContextValidationSupport.StringConceptProperty) next; + if (next instanceof IValidationSupport.StringConceptProperty) { + IValidationSupport.StringConceptProperty prop = (IValidationSupport.StringConceptProperty) next; ParametersUtil.addPartString(theContext, property, "value", prop.getValue()); - } else if (next instanceof IContextValidationSupport.CodingConceptProperty) { - IContextValidationSupport.CodingConceptProperty prop = (IContextValidationSupport.CodingConceptProperty) next; + } else if (next instanceof IValidationSupport.CodingConceptProperty) { + IValidationSupport.CodingConceptProperty prop = (IValidationSupport.CodingConceptProperty) next; ParametersUtil.addPartCoding(theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay()); } else { throw new IllegalStateException("Don't know how to handle " + next.getClass()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java new file mode 100644 index 00000000000..c7667cc1fad --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java @@ -0,0 +1,96 @@ +package ca.uhn.fhir.context.support; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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 org.apache.commons.lang3.Validate; + +/** + * Options for ValueSet expansion + * + * @see IValidationSupport + */ +public class ValueSetExpansionOptions { + + private boolean myFailOnMissingCodeSystem = true; + private int myCount = 1000; + private int myOffset = 0; + + /** + * The number of codes to return. + *

+ * Default is 1000 + *

+ */ + public int getCount() { + return myCount; + } + + /** + * The number of codes to return. + *

+ * Default is 1000 + *

+ */ + public ValueSetExpansionOptions setCount(int theCount) { + Validate.isTrue(theCount >= 0, "theCount must be >= 0"); + myCount = theCount; + return this; + } + + /** + * The code index to start at (i.e the individual code index, not the page number) + */ + public int getOffset() { + return myOffset; + } + + /** + * The code index to start at (i.e the individual code index, not the page number) + */ + public ValueSetExpansionOptions setOffset(int theOffset) { + Validate.isTrue(theOffset >= 0, "theOffset must be >= 0"); + myOffset = theOffset; + return this; + } + + /** + * Should the expansion fail if a codesystem is referenced by the valueset, but + * it can not be found? + *

+ * Default is true + *

+ */ + public boolean isFailOnMissingCodeSystem() { + return myFailOnMissingCodeSystem; + } + + /** + * Should the expansion fail if a codesystem is referenced by the valueset, but + * it can not be found? + *

+ * Default is true + *

+ */ + public ValueSetExpansionOptions setFailOnMissingCodeSystem(boolean theFailOnMissingCodeSystem) { + myFailOnMissingCodeSystem = theFailOnMissingCodeSystem; + return this; + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java similarity index 73% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java index 8cd3e96f0a6..04413995318 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.fluentpath; +package ca.uhn.fhir.fhirpath; /* * #%L @@ -23,18 +23,18 @@ package ca.uhn.fhir.fluentpath; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** - * This exception is thrown if a FluentPath expression can not be executed successfully + * This exception is thrown if a FHIRPath expression can not be executed successfully * for any reason */ -public class FluentPathExecutionException extends InternalErrorException { +public class FhirPathExecutionException extends InternalErrorException { private static final long serialVersionUID = 1L; - public FluentPathExecutionException(Throwable theCause) { + public FhirPathExecutionException(Throwable theCause) { super(theCause); } - public FluentPathExecutionException(String theMessage) { + public FhirPathExecutionException(String theMessage) { super(theMessage); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java similarity index 96% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java index 8d225b20c27..a15f716c37f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.fluentpath; +package ca.uhn.fhir.fhirpath; /* * #%L @@ -25,7 +25,7 @@ import java.util.Optional; import org.hl7.fhir.instance.model.api.IBase; -public interface IFluentPath { +public interface IFhirPath { /** * Apply the given FluentPath expression against the given input and return diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java index 23043a4b2d2..a215eca90e2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java @@ -43,6 +43,7 @@ public class HapiLocalizer { private List myBundle = new ArrayList<>(); private final Map myHardcodedMessages = new HashMap<>(); private String[] myBundleNames; + private Locale myLocale = Locale.getDefault(); public HapiLocalizer() { this(HapiLocalizer.class.getPackage().getName() + ".hapi-messages"); @@ -164,7 +165,11 @@ public class HapiLocalizer { } } - /** + public Locale getLocale() { + return myLocale; + } + + /** * This global setting causes the localizer to fail if any attempts * are made to retrieve a key that does not exist. This method is primarily for * unit tests. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java index 8a77920728e..41c8a4c63f8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.interceptor.api; */ import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java index 04a6d513bbc..96d98041172 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IInterceptorService.java @@ -23,6 +23,8 @@ package ca.uhn.fhir.interceptor.api; import javax.annotation.Nullable; import java.util.Collection; import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; public interface IInterceptorService extends IInterceptorBroadcaster { @@ -90,4 +92,8 @@ public interface IInterceptorService extends IInterceptorBroadcaster { void registerInterceptors(@Nullable Collection theInterceptors); + /** + * Unregisters all interceptors that are indicated by the given callback function returning true + */ + void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 634ccade76e..3a5d5a9f189 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -27,7 +27,12 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import javax.annotation.Nonnull; -import java.util.*; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Value for {@link Hook#value()} @@ -60,12 +65,16 @@ public enum Pointcut { *
  • * ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the request *
  • + *
  • + * ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request + *
  • * *

    * Hook methods must return void. */ CLIENT_REQUEST(void.class, - "ca.uhn.fhir.rest.client.api.IHttpRequest" + "ca.uhn.fhir.rest.client.api.IHttpRequest", + "ca.uhn.fhir.rest.client.api.IRestfulClient" ), /** @@ -77,7 +86,12 @@ public enum Pointcut { *
      *
    • * ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the request - * ca.uhn.fhir.rest.client.api.IHttpRequest - The details of the response + *
    • + *
    • + * ca.uhn.fhir.rest.client.api.IHttpResponse - The details of the response + *
    • + *
    • + * ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request *
    • *
    *

    @@ -85,7 +99,8 @@ public enum Pointcut { */ CLIENT_RESPONSE(void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", - "ca.uhn.fhir.rest.client.api.IHttpResponse" + "ca.uhn.fhir.rest.client.api.IHttpResponse", + "ca.uhn.fhir.rest.client.api.IRestfulClient" ), /** @@ -374,6 +389,44 @@ public enum Pointcut { ), + /** + * Server Hook: + * This method is called when a stream writer is generated that will be used to stream a non-binary response to + * a client. Hooks may return a wrapped writer which adds additional functionality as needed. + * + *

    + * Hooks may accept the following parameters: + *

      + *
    • + * java.io.Writer - The response writing Writer. Typically a hook will wrap this writer and layer additional functionality + * into the wrapping writer. + *
    • + *
    • + * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. + *
    • + *
    • + * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will + * only be populated when operating in a RestfulServer implementation. It is provided as a convenience. + *
    • + *
    + *

    + *

    + * Hook methods should return a {@link Writer} instance that will be used to stream the response. Hook methods + * should not throw any exception. + *

    + * + * @since 5.0.0 + */ + SERVER_OUTGOING_WRITER_CREATED(Writer.class, + "java.io.Writer", + "ca.uhn.fhir.rest.api.server.RequestDetails", + "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" + ), + /** * Server Hook: @@ -551,7 +604,7 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *

      - *
    • ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage - Hooks may modify this parameter. This will affect the checking process.
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - Hooks may modify this parameter. This will affect the checking process.
    • *
    *

    *

    @@ -560,7 +613,7 @@ public enum Pointcut { * returns false, subscription processing will not proceed for the given resource; *

    */ - SUBSCRIPTION_RESOURCE_MODIFIED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage"), + SUBSCRIPTION_RESOURCE_MODIFIED(boolean.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage"), /** @@ -574,8 +627,8 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
    • *
    • ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult
    • *
    *

    @@ -584,7 +637,7 @@ public enum Pointcut { * returns false, delivery will be aborted. *

    */ - SUBSCRIPTION_RESOURCE_MATCHED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage", "ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult"), + SUBSCRIPTION_RESOURCE_MATCHED(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage", "ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult"), /** * Subscription Hook: @@ -593,14 +646,14 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *

      - *
    • ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage - Hooks should not modify this parameter as changes will not have any effect.
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - Hooks should not modify this parameter as changes will not have any effect.
    • *
    *

    *

    * Hooks should return void. *

    */ - SUBSCRIPTION_RESOURCE_DID_NOT_MATCH_ANY_SUBSCRIPTIONS(void.class, "ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage"), + SUBSCRIPTION_RESOURCE_DID_NOT_MATCH_ANY_SUBSCRIPTIONS(void.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage"), /** * Subscription Hook: @@ -613,8 +666,8 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
    • *
    *

    * Hooks may return void or may return a boolean. If the method returns @@ -622,7 +675,7 @@ public enum Pointcut { * returns false, processing will be aborted. *

    */ - SUBSCRIPTION_BEFORE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"), + SUBSCRIPTION_BEFORE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), /** * Subscription Hook: @@ -632,14 +685,14 @@ public enum Pointcut { * Hooks may accept the following parameters: *

    *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
    • *
    *

    * Hooks should return void. *

    */ - SUBSCRIPTION_AFTER_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"), + SUBSCRIPTION_AFTER_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), /** @@ -651,7 +704,7 @@ public enum Pointcut { *

    *
      *
    • java.lang.Exception - The exception that caused the failure. Note this could be an exception thrown by a SUBSCRIPTION_BEFORE_DELIVERY or SUBSCRIPTION_AFTER_DELIVERY interceptor
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage - the message that triggered the exception
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage - the message that triggered the exception
    • *
    • java.lang.Exception
    • *
    *

    @@ -663,7 +716,7 @@ public enum Pointcut { * taken for the delivery. *

    */ - SUBSCRIPTION_AFTER_DELIVERY_FAILED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage", "java.lang.Exception"), + SUBSCRIPTION_AFTER_DELIVERY_FAILED(boolean.class, "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage", "java.lang.Exception"), /** * Subscription Hook: @@ -674,14 +727,14 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
    • *
    *

    * Hooks should return void. *

    */ - SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"), + SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), /** * Subscription Hook: @@ -693,8 +746,8 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • - *
    • ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
    • *
    *

    * Hooks may return void or may return a boolean. If the method returns @@ -702,7 +755,7 @@ public enum Pointcut { * returns false, processing will be aborted. *

    */ - SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage"), + SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), /** * Subscription Hook: @@ -712,7 +765,7 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *

      - *
    • ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage - Hooks may modify this parameter. This will affect the checking process.
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - Hooks may modify this parameter. This will affect the checking process.
    • *
    *

    *

    @@ -721,7 +774,7 @@ public enum Pointcut { * returns false, processing will be aborted. *

    */ - SUBSCRIPTION_BEFORE_PERSISTED_RESOURCE_CHECKED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage"), + SUBSCRIPTION_BEFORE_PERSISTED_RESOURCE_CHECKED(boolean.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage"), /** @@ -732,14 +785,14 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *

      - *
    • ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage - This parameter should not be modified as processing is complete when this hook is invoked.
    • + *
    • ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - This parameter should not be modified as processing is complete when this hook is invoked.
    • *
    *

    *

    * Hooks should return void. *

    */ - SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED(void.class, "ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage"), + SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED(void.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage"), /** @@ -753,13 +806,13 @@ public enum Pointcut { *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
    • + *
    • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
    • *
    *

    * Hooks should return void. *

    */ - SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED(void.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription"), + SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription"), /** @@ -804,7 +857,7 @@ public enum Pointcut { void.class, "ca.uhn.fhir.rest.api.server.RequestDetails", "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails", - "ca.uhn.fhir.jpa.delete.DeleteConflictList", + "ca.uhn.fhir.jpa.api.model.DeleteConflictList", "org.hl7.fhir.instance.model.api.IBaseResource" ), @@ -1205,13 +1258,13 @@ public enum Pointcut { /** * Storage Hook: - * Invoked when a resource delete operation is about to fail due to referential integrity conflicts. + * Invoked when a resource delete operation is about to fail due to referential integrity hcts. *

    * Hooks will have access to the list of resources that have references to the resource being deleted. *

    * Hooks may accept the following parameters: *
      - *
    • ca.uhn.fhir.jpa.delete.DeleteConflictList - The list of delete conflicts
    • + *
    • ca.uhn.fhir.jpa.api.model.DeleteConflictList - The list of delete conflicts
    • *
    • * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been @@ -1236,7 +1289,7 @@ public enum Pointcut { // Return type "ca.uhn.fhir.jpa.delete.DeleteConflictOutcome", // Params - "ca.uhn.fhir.jpa.delete.DeleteConflictList", + "ca.uhn.fhir.jpa.api.model.DeleteConflictList", "ca.uhn.fhir.rest.api.server.RequestDetails", "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" ), @@ -1321,6 +1374,83 @@ public enum Pointcut { "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" ), + /** + * Storage Hook: + * Invoked before FHIR create operation to request the identification of the partition ID to be associated + * with the resource being created. This hook will only be called if partitioning is enabled in the JPA + * server. + *

      + * Hooks may accept the following parameters: + *

      + *
        + *
      • + * org.hl7.fhir.instance.model.api.IBaseResource - The resource that will be created and needs a tenant ID assigned. + *
      • + *
      • + * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. Note that the bean + * properties are not all guaranteed to be populated, depending on how early during processing the + * exception occurred. + *
      • + *
      • + * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will + * only be populated when operating in a RestfulServer implementation. It is provided as a convenience. + *
      • + *
      + *

      + * Hooks should return an instance of ca.uhn.fhir.jpa.api.model.RequestPartitionId or null. + *

      + */ + STORAGE_PARTITION_IDENTIFY_CREATE( + // Return type + "ca.uhn.fhir.interceptor.model.RequestPartitionId", + // Params + "org.hl7.fhir.instance.model.api.IBaseResource", + "ca.uhn.fhir.rest.api.server.RequestDetails", + "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" + ), + + /** + * Storage Hook: + * Invoked before FHIR read/access operation (e.g. read/vread, search, history, etc.) operation to request the + * identification of the partition ID to be associated with the resource(s) being searched for, read, etc. + *

      + * This hook will only be called if + * partitioning is enabled in the JPA server. + *

      + *

      + * Hooks may accept the following parameters: + *

      + *
        + *
      • + * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. Note that the bean + * properties are not all guaranteed to be populated, depending on how early during processing the + * exception occurred. + *
      • + *
      • + * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will + * only be populated when operating in a RestfulServer implementation. It is provided as a convenience. + *
      • + *
      + *

      + * Hooks should return an instance of ca.uhn.fhir.jpa.api.model.RequestPartitionId or null. + *

      + */ + STORAGE_PARTITION_IDENTIFY_READ( + // Return type + "ca.uhn.fhir.interceptor.model.RequestPartitionId", + // Params + "ca.uhn.fhir.rest.api.server.RequestDetails", + "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" + ), + /** * Performance Tracing Hook: * This hook is invoked when any informational messages generated by the @@ -1643,12 +1773,12 @@ public enum Pointcut { *

      *

      * THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING. - *

      - *

      + *

      + *

      * Note that this is a performance tracing hook. Use with caution in production * systems, since calling it may (or may not) carry a cost. - *

      - *

      + *

      + *

      * Hooks may accept the following parameters: *

      *
        @@ -1722,9 +1852,7 @@ public enum Pointcut { * This pointcut is used only for unit tests. Do not use in production code as it may be changed or * removed at any time. */ - TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName()) - - ; + TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName()); private final List myParameterTypes; private final Class myReturnType; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java index 0c01735b266..a6ff71a222b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java @@ -20,7 +20,13 @@ package ca.uhn.fhir.interceptor.executor; * #L% */ -import ca.uhn.fhir.interceptor.api.*; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; @@ -41,6 +47,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; import java.util.stream.Collectors; public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster { @@ -145,6 +152,16 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa } } + @Override + public void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction) { + unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers); + unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers); + } + + private void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction, ListMultimap theGlobalInvokers) { + theGlobalInvokers.entries().removeIf(t->theShouldUnregisterFunction.test(t.getValue().getInterceptor())); + } + @Override public boolean registerThreadLocalInterceptor(Object theInterceptor) { if (!myThreadlocalInvokersEnabled) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java new file mode 100644 index 00000000000..c1d0f8291c1 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/RequestPartitionId.java @@ -0,0 +1,106 @@ +package ca.uhn.fhir.interceptor.model; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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 javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.LocalDate; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +public class RequestPartitionId { + + private final Integer myPartitionId; + private final LocalDate myPartitionDate; + private final String myPartitionName; + + /** + * Constructor + */ + private RequestPartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { + myPartitionName = thePartitionName; + myPartitionId = thePartitionId; + myPartitionDate = thePartitionDate; + } + + public String getPartitionName() { + return myPartitionName; + } + + @Nullable + public Integer getPartitionId() { + return myPartitionId; + } + + @Nullable + public LocalDate getPartitionDate() { + return myPartitionDate; + } + + @Override + public String toString() { + return getPartitionIdStringOrNullString(); + } + + /** + * Returns the partition ID (numeric) as a string, or the string "null" + */ + public String getPartitionIdStringOrNullString() { + return defaultIfNull(myPartitionId, "null").toString(); + } + + /** + * Create a string representation suitable for use as a cache key. Null aware. + */ + public static String stringifyForKey(RequestPartitionId theRequestPartitionId) { + String retVal = "(null)"; + if (theRequestPartitionId != null) { + retVal = theRequestPartitionId.getPartitionIdStringOrNullString(); + } + return retVal; + } + + @Nonnull + public static RequestPartitionId fromPartitionId(@Nullable Integer thePartitionId) { + return fromPartitionId(thePartitionId, null); + } + + @Nonnull + public static RequestPartitionId fromPartitionId(@Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { + return new RequestPartitionId(null, thePartitionId, thePartitionDate); + } + + @Nonnull + public static RequestPartitionId fromPartitionName(@Nullable String thePartitionName) { + return fromPartitionName(thePartitionName, null); + } + + @Nonnull + public static RequestPartitionId fromPartitionName(@Nullable String thePartitionName, @Nullable LocalDate thePartitionDate) { + return new RequestPartitionId(thePartitionName, null, thePartitionDate); + } + + @Nonnull + public static RequestPartitionId forPartitionNameAndId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { + return new RequestPartitionId(thePartitionName, thePartitionId, thePartitionDate); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java index 53ecca5471e..d1c5b270631 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java @@ -23,11 +23,10 @@ package ca.uhn.fhir.model.api; import java.io.InputStream; import java.util.Date; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.*; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; /** @@ -39,9 +38,7 @@ import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; */ public interface IFhirVersion { - IFluentPath createFluentPathExecutor(FhirContext theFhirContext); - - IContextValidationSupport createValidationSupport(); + IFhirPath createFhirPathExecutor(FhirContext theFhirContext); IBaseResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java new file mode 100644 index 00000000000..8211fe1d838 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.model.api; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public interface IModelJson { +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java index f8a4193cf16..c73c545c407 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.instance.model.api.IBase; @@ -120,7 +120,7 @@ public abstract class BaseNarrativeGenerator implements INarrativeGenerator { if (theFhirContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { return Collections.singletonList(theResource); } - IFluentPath fhirPath = theFhirContext.newFluentPath(); + IFhirPath fhirPath = theFhirContext.newFluentPath(); return fhirPath.evaluate(theResource, theContextPath, IBase.class); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/IResourceModifiedConsumer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseErrorHandler.java similarity index 71% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/IResourceModifiedConsumer.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseErrorHandler.java index 40924bdfbf0..0b9076fe5c6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/IResourceModifiedConsumer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseErrorHandler.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.parser; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,8 +20,15 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +class BaseErrorHandler { + + String describeLocation(IParserErrorHandler.IParseLocation theLocation) { + if (theLocation == null) { + return ""; + } else { + return theLocation.toString() + " "; + } + } -public interface IResourceModifiedConsumer { - void submitResourceModified(ResourceModifiedMessage theMsg); } + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 7f2bc3d3a2d..4a1318c2a72 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -58,6 +58,14 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @SuppressWarnings("WeakerAccess") public abstract class BaseParser implements IParser { + /** + * Any resources that were created by the parser (i.e. by parsing a serialized resource) will have + * a {@link IBaseResource#getUserData(String) user data} property with this key. + * + * @since 5.0.0 + */ + public static final String RESOURCE_CREATED_BY_PARSER = BaseParser.class.getName() + "_" + "RESOURCE_CREATED_BY_PARSER"; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); private static final Set notEncodeForContainedResource = new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated")); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 8cc1a40c4ff..6a5917853ed 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -22,20 +22,29 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseContainedDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.narrative.INarrativeGenerator; -import ca.uhn.fhir.parser.json.*; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.ElementUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.text.WordUtils; @@ -45,11 +54,17 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE; import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; /** * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use @@ -147,10 +162,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.beginObject(arrayName); } - private JsonLikeWriter createJsonWriter(Writer theWriter) { - JsonLikeStructure jsonStructure = new GsonStructure(); - JsonLikeWriter retVal = jsonStructure.getJsonLikeWriter(theWriter); - return retVal; + private JsonLikeWriter createJsonWriter(Writer theWriter) throws IOException { + JsonLikeStructure jsonStructure = new JacksonStructure(); + return jsonStructure.getJsonLikeWriter(theWriter); } public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException { @@ -160,7 +174,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.init(); RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); - encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext); + encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext); theEventWriter.flush(); } @@ -168,11 +182,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException { JsonLikeWriter eventWriter = createJsonWriter(theWriter); doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext); + eventWriter.close(); } @Override public T doParseResource(Class theResourceType, Reader theReader) { - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(theReader); T retVal = doParseResource(theResourceType, jsonStructure); @@ -243,9 +258,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { break; } else if (valueObj instanceof Long) { if (theChildName != null) { - theEventWriter.write(theChildName, (long)valueObj); + theEventWriter.write(theChildName, (long) valueObj); } else { - theEventWriter.write((long)valueObj); + theEventWriter.write((long) valueObj); } break; } @@ -302,19 +317,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } case CONTAINED_RESOURCE_LIST: case CONTAINED_RESOURCES: { - /* - * Disabled per #103 ContainedDt value = (ContainedDt) theNextValue; for (IResource next : - * value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; } - * encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, - * fixContainedResourceId(next.getId().getValue())); } - */ List containedResources = getContainedResources().getContainedResources(); if (containedResources.size() > 0) { beginArray(theEventWriter, theChildName); for (IBaseResource next : containedResources) { IIdType resourceId = getContainedResources().getResourceId(next); - encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext); + String value = resourceId.getValue(); + encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(value), theEncodeContext); } theEventWriter.endArray(); @@ -344,7 +354,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { RuntimeResourceDefinition def = myContext.getResourceDefinition(resource); theEncodeContext.pushPath(def.getName(), true); - encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, theEncodeContext); + encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext); theEncodeContext.popPath(); break; @@ -418,10 +428,10 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { String currentChildName = null; boolean inArray = false; - ArrayList> extensions = new ArrayList>(0); - ArrayList> modifierExtensions = new ArrayList>(0); - ArrayList> comments = new ArrayList>(0); - ArrayList ids = new ArrayList(0); + ArrayList> extensions = new ArrayList<>(0); + ArrayList> modifierExtensions = new ArrayList<>(0); + ArrayList> comments = new ArrayList<>(0); + ArrayList ids = new ArrayList<>(0); int valueIdx = 0; for (IBase nextValue : values) { @@ -594,7 +604,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { return maxCardinality > 1 || maxCardinality == Child.MAX_UNLIMITED; } - private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException { + private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException { writeCommentsPreAndPost(theNextValue, theEventWriter); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext); @@ -1107,7 +1117,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } else { // must be a SCALAR theState.enteringNewElement(null, theName); - theState.attributeValue("value", theJsonVal.getAsString()); + String asString = theJsonVal.getAsString(); + theState.attributeValue("value", asString); parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName); theState.endingElement(); } @@ -1376,15 +1387,6 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } } - public static Gson newGson() { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - return gson; - } - - private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException { - theWriter.write(theName, theValue); - } - private class HeldExtension implements Comparable { private CompositeChildElement myChildElem; @@ -1479,7 +1481,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext, theContainedResource); } else { String childName = myDef.getChildNameByDatatype(myValue.getClass()); - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext); managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext, theContainedResource); } @@ -1556,7 +1558,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (childDef == null) { throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); } - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent,false, theEncodeContext); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent, false, theEncodeContext); managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext, theContainedResource); theEncodeContext.popPath(); @@ -1568,4 +1570,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endObject(); } } + + private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException { + theWriter.write(theName, theValue); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java index a43a429508c..49f62bb8553 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java @@ -38,7 +38,7 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; * @see IParser#setParserErrorHandler(IParserErrorHandler) * @see FhirContext#setParserErrorHandler(IParserErrorHandler) */ -public class LenientErrorHandler implements IParserErrorHandler { +public class LenientErrorHandler extends BaseErrorHandler implements IParserErrorHandler { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class); private static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler(); @@ -84,7 +84,7 @@ public class LenientErrorHandler implements IParserErrorHandler { public void invalidValue(IParseLocation theLocation, String theValue, String theError) { if (isBlank(theValue) || myErrorOnInvalidValue == false) { if (myLogErrors) { - ourLog.warn("Invalid attribute value \"{}\": {}", theValue, theError); + ourLog.warn("{}Invalid attribute value \"{}\": {}", describeLocation(theLocation), theValue, theError); } } else { STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError); @@ -133,35 +133,35 @@ public class LenientErrorHandler implements IParserErrorHandler { @Override public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { if (myLogErrors) { - ourLog.warn("Multiple repetitions of non-repeatable element '{}' found while parsing", theElementName); + ourLog.warn("{}Multiple repetitions of non-repeatable element '{}' found while parsing", describeLocation(theLocation), theElementName); } } @Override public void unknownAttribute(IParseLocation theLocation, String theElementName) { if (myLogErrors) { - ourLog.warn("Unknown attribute '{}' found while parsing", theElementName); + ourLog.warn("{}Unknown attribute '{}' found while parsing",describeLocation(theLocation), theElementName); } } @Override public void unknownElement(IParseLocation theLocation, String theElementName) { if (myLogErrors) { - ourLog.warn("Unknown element '{}' found while parsing", theElementName); + ourLog.warn("{}Unknown element '{}' found while parsing", describeLocation(theLocation), theElementName); } } @Override public void unknownReference(IParseLocation theLocation, String theReference) { if (myLogErrors) { - ourLog.warn("Resource has invalid reference: {}", theReference); + ourLog.warn("{}Resource has invalid reference: {}", describeLocation(theLocation), theReference); } } @Override public void extensionContainsValueAndNestedExtensions(IParseLocation theLocation) { if (myLogErrors) { - ourLog.warn("Extension contains both a value and nested extensions: {}", theLocation); + ourLog.warn("{}Extension contains both a value and nested extensions", describeLocation(theLocation)); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParseLocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParseLocation.java index f8451143806..fc150cdf2fc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParseLocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParseLocation.java @@ -54,6 +54,13 @@ class ParseLocation implements IParseLocation { @Override public String toString() { - return defaultString(myParentElementName); + return "[element=\"" + defaultString(myParentElementName) + "\"]"; + } + + /** + * Factory method + */ + static ParseLocation fromElementName(String theChildName) { + return new ParseLocation(theChildName); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index b5eb3ec6137..b4dcbd7e6e7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -43,6 +43,7 @@ import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.XmlUtil; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.api.*; @@ -191,7 +192,7 @@ class ParserState { return thePrimitiveTarget.newInstance(theDefinition.getInstanceConstructorArguments()); } - public IPrimitiveType getPrimitiveInstance(BaseRuntimeChildDefinition theChild, RuntimePrimitiveDatatypeDefinition thePrimitiveTarget) { + public IPrimitiveType getPrimitiveInstance(BaseRuntimeChildDefinition theChild, RuntimePrimitiveDatatypeDefinition thePrimitiveTarget, String theChildName) { return thePrimitiveTarget.newInstance(theChild.getInstanceConstructorArguments()); } @@ -456,7 +457,7 @@ class ParserState { RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; IPrimitiveType newChildInstance = newPrimitiveInstance(myDefinition, primitiveTarget); myDefinition.getMutator().addValue(myParentInstance, newChildInstance); - PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance); + PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart, primitiveTarget.getName()); push(newState); return; } @@ -495,10 +496,10 @@ class ParserState { private class ElementCompositeState extends BaseState { - private BaseRuntimeElementCompositeDefinition myDefinition; - private IBase myInstance; - private Set myParsedNonRepeatableNames = new HashSet<>(); - private String myElementName; + private final BaseRuntimeElementCompositeDefinition myDefinition; + private final IBase myInstance; + private final Set myParsedNonRepeatableNames = new HashSet<>(); + private final String myElementName; ElementCompositeState(PreResourceState thePreResourceState, String theElementName, BaseRuntimeElementCompositeDefinition theDef, IBase theInstance) { super(thePreResourceState); @@ -583,9 +584,9 @@ class ParserState { case PRIMITIVE_DATATYPE: { RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; IPrimitiveType newChildInstance; - newChildInstance = getPrimitiveInstance(child, primitiveTarget); + newChildInstance = getPrimitiveInstance(child, primitiveTarget, theChildName); child.getMutator().addValue(myInstance, newChildInstance); - PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance); + PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theChildName, primitiveTarget.getName()); push(newState); return; } @@ -668,7 +669,7 @@ class ParserState { public class ElementIdState extends BaseState { - private IBaseElement myElement; + private final IBaseElement myElement; ElementIdState(ParserState.PreResourceState thePreResourceState, IBaseElement theElement) { super(thePreResourceState); @@ -689,7 +690,7 @@ class ParserState { private class ExtensionState extends BaseState { - private IBaseExtension myExtension; + private final IBaseExtension myExtension; ExtensionState(PreResourceState thePreResourceState, IBaseExtension theExtension) { super(thePreResourceState); @@ -752,7 +753,7 @@ class ParserState { RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; IPrimitiveType newChildInstance = newInstance(primitiveTarget); myExtension.setValue(newChildInstance); - PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance); + PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart, primitiveTarget.getName()); push(newState); return; } @@ -782,7 +783,7 @@ class ParserState { public class IdentifiableElementIdState extends BaseState { - private IIdentifiableElement myElement; + private final IIdentifiableElement myElement; public IdentifiableElementIdState(ParserState.PreResourceState thePreResourceState, IIdentifiableElement theElement) { super(thePreResourceState); @@ -802,7 +803,7 @@ class ParserState { } private class MetaElementState extends BaseState { - private ResourceMetadataMap myMap; + private final ResourceMetadataMap myMap; public MetaElementState(ParserState.PreResourceState thePreResourceState, ResourceMetadataMap theMap) { super(thePreResourceState); @@ -824,7 +825,7 @@ class ParserState { break; case "lastUpdated": InstantDt updated = new InstantDt(); - push(new PrimitiveState(getPreResourceState(), updated)); + push(new PrimitiveState(getPreResourceState(), updated, theLocalPart, "instant")); myMap.put(ResourceMetadataKeyEnum.UPDATED, updated); break; case "security": @@ -850,7 +851,7 @@ class ParserState { newProfiles = new ArrayList<>(1); } IdDt profile = new IdDt(); - push(new PrimitiveState(getPreResourceState(), profile)); + push(new PrimitiveState(getPreResourceState(), profile, theLocalPart, "id")); newProfiles.add(profile); myMap.put(ResourceMetadataKeyEnum.PROFILES, Collections.unmodifiableList(newProfiles)); break; @@ -891,7 +892,7 @@ class ParserState { private class MetaVersionElementState extends BaseState { - private ResourceMetadataMap myMap; + private final ResourceMetadataMap myMap; MetaVersionElementState(ParserState.PreResourceState thePreResourceState, ResourceMetadataMap theMap) { super(thePreResourceState); @@ -1048,6 +1049,8 @@ class ParserState { } } + myInstance.setUserData(BaseParser.RESOURCE_CREATED_BY_PARSER, Boolean.TRUE); + populateTarget(); } @@ -1074,7 +1077,7 @@ class ParserState { */ for (IBaseResource next : myGlobalResources) { IIdType id = next.getIdElement(); - if (id != null && id.isEmpty() == false) { + if (id != null && !id.isEmpty()) { String resName = myContext.getResourceDefinition(next).getName(); IIdType idType = id.withResourceType(resName).toUnqualifiedVersionless(); idToResource.put(idType.getValueAsString(), next); @@ -1082,10 +1085,11 @@ class ParserState { } for (IBaseReference nextRef : myGlobalReferences) { - if (nextRef.isEmpty() == false && nextRef.getReferenceElement() != null) { + if (!nextRef.isEmpty() && nextRef.getReferenceElement() != null) { IIdType unqualifiedVersionless = nextRef.getReferenceElement().toUnqualifiedVersionless(); IBaseResource target = idToResource.get(unqualifiedVersionless.getValueAsString()); - if (target != null) { + // resource can already be filled with local contained resource by populateTarget() + if (target != null && nextRef.getResource() == null) { nextRef.setResource(target); } } @@ -1267,37 +1271,63 @@ class ParserState { } private class PrimitiveState extends BaseState { + private final String myChildName; + private final String myTypeName; private IPrimitiveType myInstance; - PrimitiveState(PreResourceState thePreResourceState, IPrimitiveType theInstance) { + PrimitiveState(PreResourceState thePreResourceState, IPrimitiveType theInstance, String theChildName, String theTypeName) { super(thePreResourceState); myInstance = theInstance; + myChildName = theChildName; + myTypeName = theTypeName; } @Override public void attributeValue(String theName, String theValue) throws DataFormatException { + String value = theValue; if ("value".equals(theName)) { - if ("".equals(theValue)) { - myErrorHandler.invalidValue(null, theValue, "Attribute values must not be empty (\"\")"); + if ("".equals(value)) { + ParseLocation location = ParseLocation.fromElementName(myChildName); + myErrorHandler.invalidValue(location, value, "Attribute value must not be empty (\"\")"); } else { + + /* + * It may be possible to clean this up somewhat once the following PR is hopefully merged: + * https://github.com/FasterXML/jackson-core/pull/611 + * + * See TolerantJsonParser + */ + if ("decimal".equals(myTypeName)) { + if (value != null) + if (value.startsWith(".") && NumberUtils.isDigits(value.substring(1))) { + value = "0" + value; + } else { + while (value.startsWith("00")) { + value = value.substring(1); + } + } + } + try { - myInstance.setValueAsString(theValue); + myInstance.setValueAsString(value); } catch (DataFormatException | IllegalArgumentException e) { - myErrorHandler.invalidValue(null, theValue, e.getMessage()); + ParseLocation location = ParseLocation.fromElementName(myChildName); + myErrorHandler.invalidValue(location, value, e.getMessage()); } } } else if ("id".equals(theName)) { if (myInstance instanceof IIdentifiableElement) { - ((IIdentifiableElement) myInstance).setElementSpecificId(theValue); + ((IIdentifiableElement) myInstance).setElementSpecificId(value); } else if (myInstance instanceof IBaseElement) { - ((IBaseElement) myInstance).setId(theValue); + ((IBaseElement) myInstance).setId(value); } else if (myInstance instanceof IBaseResource) { - new IdDt(theValue).applyTo((org.hl7.fhir.instance.model.api.IBaseResource) myInstance); + new IdDt(value).applyTo((org.hl7.fhir.instance.model.api.IBaseResource) myInstance); } else { - myErrorHandler.unknownAttribute(null, theName); + ParseLocation location = ParseLocation.fromElementName(myChildName); + myErrorHandler.unknownAttribute(location, theName); } } else { - super.attributeValue(theName, theValue); + super.attributeValue(theName, value); } } @@ -1306,17 +1336,6 @@ class ParserState { pop(); } - // @Override - // public void enteringNewElementExtension(StartElement theElement, - // String theUrlAttr) { - // if (myInstance instanceof ISupportsUndeclaredExtensions) { - // UndeclaredExtension ext = new UndeclaredExtension(theUrlAttr); - // ((ISupportsUndeclaredExtensions) - // myInstance).getUndeclaredExtensions().add(ext); - // push(new ExtensionState(ext)); - // } - // } - @Override public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException { super.enteringNewElement(theNamespaceUri, theLocalPart); @@ -1342,7 +1361,7 @@ class ParserState { @Override public void enteringNewElement(String theNamespace, String theChildName) throws DataFormatException { if ("id".equals(theChildName)) { - push(new PrimitiveState(getPreResourceState(), myInstance.getId())); + push(new PrimitiveState(getPreResourceState(), myInstance.getId(), theChildName, "id")); } else if ("meta".equals(theChildName)) { push(new MetaElementState(getPreResourceState(), myInstance.getResourceMetadata())); } else { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java index 433b3f0cbf5..4030b54c11c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; +import ca.uhn.fhir.util.UrlUtil; /* * #%L @@ -31,7 +32,7 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; * @see IParser#setParserErrorHandler(IParserErrorHandler) * @see FhirContext#setParserErrorHandler(IParserErrorHandler) */ -public class StrictErrorHandler implements IParserErrorHandler { +public class StrictErrorHandler extends BaseErrorHandler implements IParserErrorHandler { @Override public void containedResourceWithNoId(IParseLocation theLocation) { @@ -46,7 +47,7 @@ public class StrictErrorHandler implements IParserErrorHandler { @Override public void invalidValue(IParseLocation theLocation, String theValue, String theError) { - throw new DataFormatException("Invalid attribute value \"" + theValue + "\": " + theError); + throw new DataFormatException(describeLocation(theLocation) + "Invalid attribute value \"" + UrlUtil.sanitizeUrlPart(theValue) + "\": " + theError); } @Override @@ -65,27 +66,27 @@ public class StrictErrorHandler implements IParserErrorHandler { @Override public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { - throw new DataFormatException("Multiple repetitions of non-repeatable element '" + theElementName + "' found during parse"); + throw new DataFormatException(describeLocation(theLocation) + "Multiple repetitions of non-repeatable element '" + theElementName + "' found during parse"); } @Override public void unknownAttribute(IParseLocation theLocation, String theAttributeName) { - throw new DataFormatException("Unknown attribute '" + theAttributeName + "' found during parse"); + throw new DataFormatException(describeLocation(theLocation) + "Unknown attribute '" + theAttributeName + "' found during parse"); } @Override public void unknownElement(IParseLocation theLocation, String theElementName) { - throw new DataFormatException("Unknown element '" + theElementName + "' found during parse"); + throw new DataFormatException(describeLocation(theLocation) + "Unknown element '" + theElementName + "' found during parse"); } @Override public void unknownReference(IParseLocation theLocation, String theReference) { - throw new DataFormatException("Resource has invalid reference: " + theReference); + throw new DataFormatException(describeLocation(theLocation) + "Resource has invalid reference: " + theReference); } @Override public void extensionContainsValueAndNestedExtensions(IParseLocation theLocation) { - throw new DataFormatException("Extension contains both a value and nested extensions: " + theLocation); + throw new DataFormatException(describeLocation(theLocation) + "Extension contains both a value and nested extensions"); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 2ed7221cc16..a09ad1208be 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -287,7 +287,8 @@ public class XmlParser extends BaseParser { for (IBaseResource next : getContainedResources().getContainedResources()) { IIdType resourceId = getContainedResources().getResourceId(next); theEventWriter.writeStartElement("contained"); - encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext); + String value = resourceId.getValue(); + encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext); theEventWriter.writeEndElement(); } break; @@ -300,7 +301,7 @@ public class XmlParser extends BaseParser { } theEventWriter.writeStartElement(theChildName); theEncodeContext.pushPath(resourceName, true); - encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext); + encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext); theEncodeContext.popPath(); theEventWriter.writeEndElement(); break; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java deleted file mode 100644 index fcf96fbbfca..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonStructure.java +++ /dev/null @@ -1,379 +0,0 @@ -package ca.uhn.fhir.parser.json; -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 - 2020 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.io.PushbackReader; -import java.io.Reader; -import java.io.Writer; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import ca.uhn.fhir.parser.DataFormatException; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; - -public class GsonStructure implements JsonLikeStructure { - - private enum ROOT_TYPE {OBJECT, ARRAY}; - private ROOT_TYPE rootType = null; - private JsonElement nativeRoot = null; - private JsonLikeValue jsonLikeRoot = null; - private GsonWriter jsonLikeWriter = null; - - public GsonStructure() { - super(); - } - - public void setNativeObject (JsonObject json) { - this.rootType = ROOT_TYPE.OBJECT; - this.nativeRoot = json; - } - public void setNativeArray (JsonArray json) { - this.rootType = ROOT_TYPE.ARRAY; - this.nativeRoot = json; - } - - @Override - public JsonLikeStructure getInstance() { - return new GsonStructure(); - } - - @Override - public void load(Reader theReader) throws DataFormatException { - this.load(theReader, false); - } - - @Override - public void load(Reader theReader, boolean allowArray) throws DataFormatException { - PushbackReader pbr = new PushbackReader(theReader); - int nextInt; - try { - while(true) { - nextInt = pbr.read(); - if (nextInt == -1) { - throw new DataFormatException("Did not find any content to parse"); - } - if (nextInt == '{') { - pbr.unread(nextInt); - break; - } - if (Character.isWhitespace(nextInt)) { - continue; - } - if (allowArray) { - if (nextInt == '[') { - pbr.unread(nextInt); - break; - } - throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')"); - } - throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')"); - } - - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - if (nextInt == '{') { - JsonObject root = gson.fromJson(pbr, JsonObject.class); - setNativeObject(root); - } else if (nextInt == '[') { - JsonArray root = gson.fromJson(pbr, JsonArray.class); - setNativeArray(root); - } - } catch (JsonSyntaxException e) { - if (e.getMessage().startsWith("Unexpected char 39")) { - throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); - } - throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); - } catch (Exception e) { - throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e); - } - } - - @Override - public JsonLikeWriter getJsonLikeWriter (Writer writer) { - if (null == jsonLikeWriter) { - jsonLikeWriter = new GsonWriter(writer); - } - return jsonLikeWriter; - } - - @Override - public JsonLikeWriter getJsonLikeWriter () { - if (null == jsonLikeWriter) { - jsonLikeWriter = new GsonWriter(); - } - return jsonLikeWriter; - } - - @Override - public JsonLikeObject getRootObject() throws DataFormatException { - if (rootType == ROOT_TYPE.OBJECT) { - if (null == jsonLikeRoot) { - jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot); - } - return jsonLikeRoot.getAsObject(); - } - throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); - } - - @Override - public JsonLikeArray getRootArray() throws DataFormatException { - if (rootType == ROOT_TYPE.ARRAY) { - if (null == jsonLikeRoot) { - jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot); - } - return jsonLikeRoot.getAsArray(); - } - throw new DataFormatException("Content must be a valid JSON Array. It must start with '['."); - } - - private static class GsonJsonObject extends JsonLikeObject { - private JsonObject nativeObject; - private Set keySet = null; - private Map jsonLikeMap = new LinkedHashMap(); - - public GsonJsonObject (JsonObject json) { - this.nativeObject = json; - } - - @Override - public Object getValue() { - return null; - } - - @Override - public Set keySet() { - if (null == keySet) { - Set> entrySet = nativeObject.entrySet(); - keySet = new EntryOrderedSet(entrySet.size()); - for (Entry entry : entrySet) { - keySet.add(entry.getKey()); - } - } - return keySet; - } - - @Override - public JsonLikeValue get(String key) { - JsonLikeValue result = null; - if (jsonLikeMap.containsKey(key)) { - result = jsonLikeMap.get(key); - } else { - JsonElement child = nativeObject.get(key); - if (child != null) { - result = new GsonJsonValue(child); - } - jsonLikeMap.put(key, result); - } - return result; - } - } - - private static class GsonJsonArray extends JsonLikeArray { - private JsonArray nativeArray; - private Map jsonLikeMap = new LinkedHashMap(); - - public GsonJsonArray (JsonArray json) { - this.nativeArray = json; - } - - @Override - public Object getValue() { - return null; - } - - @Override - public int size() { - return nativeArray.size(); - } - - @Override - public JsonLikeValue get(int index) { - Integer key = Integer.valueOf(index); - JsonLikeValue result = null; - if (jsonLikeMap.containsKey(key)) { - result = jsonLikeMap.get(key); - } else { - JsonElement child = nativeArray.get(index); - if (child != null) { - result = new GsonJsonValue(child); - } - jsonLikeMap.put(key, result); - } - return result; - } - } - - private static class GsonJsonValue extends JsonLikeValue { - private JsonElement nativeValue; - private JsonLikeObject jsonLikeObject = null; - private JsonLikeArray jsonLikeArray = null; - - public GsonJsonValue (JsonElement json) { - this.nativeValue = json; - } - - @Override - public Object getValue() { - if (nativeValue != null && nativeValue.isJsonPrimitive()) { - if (((JsonPrimitive)nativeValue).isNumber()) { - return nativeValue.getAsNumber(); - } - if (((JsonPrimitive)nativeValue).isBoolean()) { - return Boolean.valueOf(nativeValue.getAsBoolean()); - } - return nativeValue.getAsString(); - } - return null; - } - - @Override - public ValueType getJsonType() { - if (null == nativeValue || nativeValue.isJsonNull()) { - return ValueType.NULL; - } - if (nativeValue.isJsonObject()) { - return ValueType.OBJECT; - } - if (nativeValue.isJsonArray()) { - return ValueType.ARRAY; - } - if (nativeValue.isJsonPrimitive()) { - return ValueType.SCALAR; - } - return null; - } - - @Override - public ScalarType getDataType() { - if (nativeValue != null && nativeValue.isJsonPrimitive()) { - if (((JsonPrimitive)nativeValue).isNumber()) { - return ScalarType.NUMBER; - } - if (((JsonPrimitive)nativeValue).isString()) { - return ScalarType.STRING; - } - if (((JsonPrimitive)nativeValue).isBoolean()) { - return ScalarType.BOOLEAN; - } - } - return null; - } - - @Override - public JsonLikeArray getAsArray() { - if (nativeValue != null && nativeValue.isJsonArray()) { - if (null == jsonLikeArray) { - jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue); - } - } - return jsonLikeArray; - } - - @Override - public JsonLikeObject getAsObject() { - if (nativeValue != null && nativeValue.isJsonObject()) { - if (null == jsonLikeObject) { - jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue); - } - } - return jsonLikeObject; - } - - @Override - public Number getAsNumber() { - return nativeValue != null ? nativeValue.getAsNumber() : null; - } - - @Override - public String getAsString() { - return nativeValue != null ? nativeValue.getAsString() : null; - } - - @Override - public boolean getAsBoolean() { - if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) { - return nativeValue.getAsBoolean(); - } - return super.getAsBoolean(); - } - } - - private static class EntryOrderedSet extends AbstractSet { - private transient ArrayList data = null; - - public EntryOrderedSet (int initialCapacity) { - data = new ArrayList(initialCapacity); - } - @SuppressWarnings("unused") - public EntryOrderedSet () { - data = new ArrayList(); - } - - @Override - public int size() { - return data.size(); - } - - @Override - public boolean contains(Object o) { - return data.contains(o); - } - - @SuppressWarnings("unused") // not really.. just not here - public T get(int index) { - return data.get(index); - } - - @Override - public boolean add(T element) { - if (data.contains(element)) { - return false; - } - return data.add(element); - } - - @Override - public boolean remove(Object o) { - return data.remove(o); - } - - @Override - public void clear() { - data.clear(); - } - - @Override - public Iterator iterator() { - return data.iterator(); - } - - } -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java deleted file mode 100644 index 1442f40c9c3..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/GsonWriter.java +++ /dev/null @@ -1,263 +0,0 @@ -package ca.uhn.fhir.parser.json; - -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 - 2020 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.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Stack; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.stream.JsonWriter; - -public class GsonWriter extends JsonLikeWriter { - private static final Logger log = LoggerFactory.getLogger(GsonWriter.class); - - private JsonWriter eventWriter; - private enum BlockType { - NONE, OBJECT, ARRAY - } - private BlockType blockType = BlockType.NONE; - private Stack blockStack = new Stack(); - - public GsonWriter () { - super(); - } - public GsonWriter (Writer writer) { - setWriter(writer); - } - - @Override - public JsonLikeWriter init() throws IOException { - eventWriter = new JsonWriter(getWriter()); - eventWriter.setSerializeNulls(true); - if (isPrettyPrint()) { - eventWriter.setIndent(" "); - } - blockType = BlockType.NONE; - blockStack.clear(); - return this; - } - - @Override - public JsonLikeWriter flush() throws IOException { - if (blockType != BlockType.NONE) { - log.error("JsonLikeStreamWriter.flush() called but JSON document is not finished"); - } - eventWriter.flush(); - getWriter().flush(); - return this; - } - - @Override - public void close() throws IOException { - eventWriter.close(); - getWriter().close(); - } - - @Override - public JsonLikeWriter beginObject() throws IOException { - blockStack.push(blockType); - blockType = BlockType.OBJECT; - eventWriter.beginObject(); - return this; - } - - @Override - public JsonLikeWriter beginArray() throws IOException { - blockStack.push(blockType); - blockType = BlockType.ARRAY; - eventWriter.beginArray(); - return this; - } - - @Override - public JsonLikeWriter beginObject(String name) throws IOException { - blockStack.push(blockType); - blockType = BlockType.OBJECT; - eventWriter.name(name); - eventWriter.beginObject(); - return this; - } - - @Override - public JsonLikeWriter beginArray(String name) throws IOException { - blockStack.push(blockType); - blockType = BlockType.ARRAY; - eventWriter.name(name); - eventWriter.beginArray(); - return this; - } - - @Override - public JsonLikeWriter write(String value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(BigInteger value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(BigDecimal value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(long value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(double value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(Boolean value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(boolean value) throws IOException { - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter writeNull() throws IOException { - eventWriter.nullValue(); - return this; - } - - @Override - public JsonLikeWriter write(String name, String value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, BigInteger value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - @Override - public JsonLikeWriter write(String name, BigDecimal value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, long value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, double value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, Boolean value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter write(String name, boolean value) throws IOException { - eventWriter.name(name); - eventWriter.value(value); - return this; - } - - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - eventWriter.name(name); - eventWriter.nullValue(); - return this; - } - - @Override - public JsonLikeWriter endObject() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); - } else { - if (blockType != BlockType.OBJECT) { - log.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); - eventWriter.endArray(); - } else { - eventWriter.endObject(); - } - blockType = blockStack.pop(); - } - return this; - } - - @Override - public JsonLikeWriter endArray() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); - } else { - if (blockType != BlockType.ARRAY) { - log.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); - eventWriter.endObject(); - } else { - eventWriter.endArray(); - } - blockType = blockStack.pop(); - } - return this; - } - - @Override - public JsonLikeWriter endBlock() throws IOException { - if (blockType == BlockType.NONE) { - log.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); - } else { - if (blockType == BlockType.ARRAY) { - eventWriter.endArray(); - } else { - eventWriter.endObject(); - } - blockType = blockStack.pop(); - } - return this; - } - -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java index dd5e785a387..fd3d3bea64b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeObject.java @@ -53,20 +53,4 @@ public abstract class JsonLikeObject extends JsonLikeValue { public abstract JsonLikeValue get (String key); - public String getString (String key) { - JsonLikeValue value = this.get(key); - if (null == value) { - throw new NullPointerException("Json object missing element named \""+key+"\""); - } - return value.getAsString(); - } - - public String getString (String key, String defaultValue) { - String result = defaultValue; - JsonLikeValue value = this.get(key); - if (value != null) { - result = value.getAsString(); - } - return result; - } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java index 5d7004c9cfd..a171d0c75bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeStructure.java @@ -19,34 +19,38 @@ */ package ca.uhn.fhir.parser.json; +import ca.uhn.fhir.parser.DataFormatException; + +import java.io.IOException; import java.io.Reader; import java.io.Writer; -import ca.uhn.fhir.parser.DataFormatException; - /** - * This interface is the generic representation of any sort of data + * This interface is the generic representation of any sort of data * structure that looks and smells like JSON. These data structures * can be abstractly viewed as a or List * whose members are other Maps, Lists, or scalars (Strings, Numbers, Boolean) - * + * * @author Bill.Denton */ public interface JsonLikeStructure { - public JsonLikeStructure getInstance(); - + JsonLikeStructure getInstance(); + /** * Parse the JSON document into the Json-like structure * so that it can be navigated. - * + * * @param theReader a Reader that will - * process the JSON input stream + * process the JSON input stream * @throws DataFormatException when invalid JSON is received */ - public void load (Reader theReader) throws DataFormatException; - public void load (Reader theReader, boolean allowArray) throws DataFormatException; - public JsonLikeObject getRootObject () throws DataFormatException; - public JsonLikeArray getRootArray () throws DataFormatException; - public JsonLikeWriter getJsonLikeWriter (); - public JsonLikeWriter getJsonLikeWriter (Writer writer); + void load(Reader theReader) throws DataFormatException; + + void load(Reader theReader, boolean allowArray) throws DataFormatException; + + JsonLikeObject getRootObject() throws DataFormatException; + + JsonLikeWriter getJsonLikeWriter(); + + JsonLikeWriter getJsonLikeWriter(Writer writer) throws IOException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java index aebcdd80a74..9e4f8576746 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/JsonLikeWriter.java @@ -29,55 +29,73 @@ public abstract class JsonLikeWriter { private boolean prettyPrint; private Writer writer; - - public void setPrettyPrint (boolean tf) { - prettyPrint = tf; - } - public boolean isPrettyPrint () { - return prettyPrint; - } - - public void setWriter (Writer writer) { - this.writer = writer; - } - public Writer getWriter () { - return writer; - } - - public abstract JsonLikeWriter init () throws IOException; - public abstract JsonLikeWriter flush () throws IOException; - public abstract void close () throws IOException; - - public abstract JsonLikeWriter beginObject () throws IOException; - public abstract JsonLikeWriter beginArray () throws IOException; - public abstract JsonLikeWriter beginObject (String name) throws IOException; - public abstract JsonLikeWriter beginArray (String name) throws IOException; - - public abstract JsonLikeWriter write (String value) throws IOException; - public abstract JsonLikeWriter write (BigInteger value) throws IOException; - public abstract JsonLikeWriter write (BigDecimal value) throws IOException; - public abstract JsonLikeWriter write (long value) throws IOException; - public abstract JsonLikeWriter write (double value) throws IOException; - public abstract JsonLikeWriter write (Boolean value) throws IOException; - public abstract JsonLikeWriter write (boolean value) throws IOException; - public abstract JsonLikeWriter writeNull () throws IOException; - - public abstract JsonLikeWriter write (String name, String value) throws IOException; - public abstract JsonLikeWriter write (String name, BigInteger value) throws IOException; - public abstract JsonLikeWriter write (String name, BigDecimal value) throws IOException; - public abstract JsonLikeWriter write (String name, long value) throws IOException; - public abstract JsonLikeWriter write (String name, double value) throws IOException; - public abstract JsonLikeWriter write (String name, Boolean value) throws IOException; - public abstract JsonLikeWriter write (String name, boolean value) throws IOException; - public abstract JsonLikeWriter writeNull (String name) throws IOException; - - public abstract JsonLikeWriter endObject () throws IOException; - public abstract JsonLikeWriter endArray () throws IOException; - public abstract JsonLikeWriter endBlock () throws IOException; - public JsonLikeWriter() { super(); } + public boolean isPrettyPrint() { + return prettyPrint; + } + + public void setPrettyPrint(boolean tf) { + prettyPrint = tf; + } + + public Writer getWriter() { + return writer; + } + + public void setWriter(Writer writer) { + this.writer = writer; + } + + public abstract JsonLikeWriter init() throws IOException; + + public abstract JsonLikeWriter flush() throws IOException; + + public abstract void close() throws IOException; + + public abstract JsonLikeWriter beginObject() throws IOException; + + public abstract JsonLikeWriter beginObject(String name) throws IOException; + + public abstract JsonLikeWriter beginArray(String name) throws IOException; + + public abstract JsonLikeWriter write(String value) throws IOException; + + public abstract JsonLikeWriter write(BigInteger value) throws IOException; + + public abstract JsonLikeWriter write(BigDecimal value) throws IOException; + + public abstract JsonLikeWriter write(long value) throws IOException; + + public abstract JsonLikeWriter write(double value) throws IOException; + + public abstract JsonLikeWriter write(Boolean value) throws IOException; + + public abstract JsonLikeWriter write(boolean value) throws IOException; + + public abstract JsonLikeWriter writeNull() throws IOException; + + public abstract JsonLikeWriter write(String name, String value) throws IOException; + + public abstract JsonLikeWriter write(String name, BigInteger value) throws IOException; + + public abstract JsonLikeWriter write(String name, BigDecimal value) throws IOException; + + public abstract JsonLikeWriter write(String name, long value) throws IOException; + + public abstract JsonLikeWriter write(String name, double value) throws IOException; + + public abstract JsonLikeWriter write(String name, Boolean value) throws IOException; + + public abstract JsonLikeWriter write(String name, boolean value) throws IOException; + + public abstract JsonLikeWriter endObject() throws IOException; + + public abstract JsonLikeWriter endArray() throws IOException; + + public abstract JsonLikeWriter endBlock() throws IOException; + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java new file mode 100644 index 00000000000..2cd9677c467 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java @@ -0,0 +1,391 @@ +package ca.uhn.fhir.parser.json.jackson; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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.parser.DataFormatException; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; +import java.io.Writer; +import java.math.BigDecimal; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class JacksonStructure implements JsonLikeStructure { + + private static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); + private JacksonWriter jacksonWriter; + private ROOT_TYPE rootType = null; + private JsonNode nativeRoot = null; + private JsonNode jsonLikeRoot = null; + + public void setNativeObject(ObjectNode objectNode) { + this.rootType = ROOT_TYPE.OBJECT; + this.nativeRoot = objectNode; + } + + public void setNativeArray(ArrayNode arrayNode) { + this.rootType = ROOT_TYPE.ARRAY; + this.nativeRoot = arrayNode; + } + + @Override + public JsonLikeStructure getInstance() { + return new JacksonStructure(); + } + + @Override + public void load(Reader theReader) throws DataFormatException { + this.load(theReader, false); + } + + @Override + public void load(Reader theReader, boolean allowArray) throws DataFormatException { + PushbackReader pbr = new PushbackReader(theReader); + int nextInt; + try { + while (true) { + nextInt = pbr.read(); + if (nextInt == -1) { + throw new DataFormatException("Did not find any content to parse"); + } + if (nextInt == '{') { + pbr.unread(nextInt); + break; + } + if (Character.isWhitespace(nextInt)) { + continue; + } + if (allowArray) { + if (nextInt == '[') { + pbr.unread(nextInt); + break; + } + throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char) nextInt + "' (must be '{' or '[')"); + } + throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char) nextInt + "' (must be '{')"); + } + + if (nextInt == '{') { + setNativeObject((ObjectNode) OBJECT_MAPPER.readTree(pbr)); + } else { + setNativeArray((ArrayNode) OBJECT_MAPPER.readTree(pbr)); + } + } catch (Exception e) { + if (e.getMessage().startsWith("Unexpected char 39")) { + throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - " + + "This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); + } + throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); + } + } + + @Override + public JsonLikeWriter getJsonLikeWriter(Writer writer) throws IOException { + if (null == jacksonWriter) { + jacksonWriter = new JacksonWriter(OBJECT_MAPPER.getFactory(), writer); + } + + return jacksonWriter; + } + + @Override + public JsonLikeWriter getJsonLikeWriter() { + if (null == jacksonWriter) { + jacksonWriter = new JacksonWriter(); + } + return jacksonWriter; + } + + @Override + public JsonLikeObject getRootObject() throws DataFormatException { + if (rootType == ROOT_TYPE.OBJECT) { + if (null == jsonLikeRoot) { + jsonLikeRoot = nativeRoot; + } + + return new JacksonJsonObject((ObjectNode) jsonLikeRoot); + } + + throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'."); + } + + private enum ROOT_TYPE {OBJECT, ARRAY} + + private static class JacksonJsonObject extends JsonLikeObject { + private final ObjectNode nativeObject; + private final Map jsonLikeMap = new LinkedHashMap<>(); + private Set keySet = null; + + public JacksonJsonObject(ObjectNode json) { + this.nativeObject = json; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public Set keySet() { + if (null == keySet) { + final Iterable> iterable = nativeObject::fields; + keySet = StreamSupport.stream(iterable.spliterator(), false) + .map(Map.Entry::getKey) + .collect(Collectors.toCollection(EntryOrderedSet::new)); + } + + return keySet; + } + + @Override + public JsonLikeValue get(String key) { + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + JsonNode child = nativeObject.get(key); + if (child != null) { + result = new JacksonJsonValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private static class EntryOrderedSet extends AbstractSet { + private final transient ArrayList data; + + public EntryOrderedSet() { + data = new ArrayList<>(); + } + + @Override + public int size() { + return data.size(); + } + + @Override + public boolean contains(Object o) { + return data.contains(o); + } + + public T get(int index) { + return data.get(index); + } + + @Override + public boolean add(T element) { + if (data.contains(element)) { + return false; + } + return data.add(element); + } + + @Override + public boolean remove(Object o) { + return data.remove(o); + } + + @Override + public void clear() { + data.clear(); + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + } + + private static class JacksonJsonArray extends JsonLikeArray { + private final ArrayNode nativeArray; + private final Map jsonLikeMap = new LinkedHashMap(); + + public JacksonJsonArray(ArrayNode json) { + this.nativeArray = json; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public int size() { + return nativeArray.size(); + } + + @Override + public JsonLikeValue get(int index) { + Integer key = index; + JsonLikeValue result = null; + if (jsonLikeMap.containsKey(key)) { + result = jsonLikeMap.get(key); + } else { + JsonNode child = nativeArray.get(index); + if (child != null) { + result = new JacksonJsonValue(child); + } + jsonLikeMap.put(key, result); + } + return result; + } + } + + private static class JacksonJsonValue extends JsonLikeValue { + private final JsonNode nativeValue; + private JsonLikeObject jsonLikeObject = null; + private JsonLikeArray jsonLikeArray = null; + + public JacksonJsonValue(JsonNode jsonNode) { + this.nativeValue = jsonNode; + } + + @Override + public Object getValue() { + if (nativeValue != null && nativeValue.isValueNode()) { + if (nativeValue.isNumber()) { + return nativeValue.numberValue(); + } + + if (nativeValue.isBoolean()) { + return nativeValue.booleanValue(); + } + + return nativeValue.asText(); + } + return null; + } + + @Override + public ValueType getJsonType() { + if (null == nativeValue || nativeValue.isNull()) { + return ValueType.NULL; + } + if (nativeValue.isObject()) { + return ValueType.OBJECT; + } + if (nativeValue.isArray()) { + return ValueType.ARRAY; + } + if (nativeValue.isValueNode()) { + return ValueType.SCALAR; + } + return null; + } + + @Override + public ScalarType getDataType() { + if (nativeValue != null && nativeValue.isValueNode()) { + if (nativeValue.isNumber()) { + return ScalarType.NUMBER; + } + if (nativeValue.isTextual()) { + return ScalarType.STRING; + } + if (nativeValue.isBoolean()) { + return ScalarType.BOOLEAN; + } + } + return null; + } + + @Override + public JsonLikeArray getAsArray() { + if (nativeValue != null && nativeValue.isArray()) { + if (null == jsonLikeArray) { + jsonLikeArray = new JacksonJsonArray((ArrayNode) nativeValue); + } + } + return jsonLikeArray; + } + + @Override + public JsonLikeObject getAsObject() { + if (nativeValue != null && nativeValue.isObject()) { + if (null == jsonLikeObject) { + jsonLikeObject = new JacksonJsonObject((ObjectNode) nativeValue); + } + } + return jsonLikeObject; + } + + @Override + public Number getAsNumber() { + return nativeValue != null ? nativeValue.numberValue() : null; + } + + @Override + public String getAsString() { + if (nativeValue != null) { + if (nativeValue instanceof DecimalNode) { + BigDecimal value = nativeValue.decimalValue(); + return value.toPlainString(); + } + return nativeValue.asText(); + } + return null; + } + + @Override + public boolean getAsBoolean() { + if (nativeValue != null && nativeValue.isValueNode() && nativeValue.isBoolean()) { + return nativeValue.asBoolean(); + } + return super.getAsBoolean(); + } + } + + private static ObjectMapper createObjectMapper() { + ObjectMapper retVal = new ObjectMapper(); + retVal = retVal.setNodeFactory(new JsonNodeFactory(true)); + retVal = retVal.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + retVal = retVal.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + retVal = retVal.disable(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION); + retVal = retVal.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); + retVal = retVal.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); + retVal = retVal.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + return retVal; + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java new file mode 100644 index 00000000000..5e8a23786f5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java @@ -0,0 +1,217 @@ +package ca.uhn.fhir.parser.json.jackson; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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.parser.json.JsonLikeWriter; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; + +import java.io.IOException; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; + +public class JacksonWriter extends JsonLikeWriter { + + private JsonGenerator myJsonGenerator; + + public JacksonWriter(JsonFactory theJsonFactory, Writer theWriter) throws IOException { + myJsonGenerator = theJsonFactory.createGenerator(theWriter); + setWriter(theWriter); + } + + public JacksonWriter() { + } + + @Override + public JsonLikeWriter init() { + if (isPrettyPrint()) { + DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() { + + /** + * Objects should serialize as + *
        +				 * {
        +				 *    "key": "value"
        +				 * }
        +				 * 
        + * in order to be consistent with Gson behaviour, instead of the jackson default + *
        +				 * {
        +				 *    "key" : "value"
        +				 * }
        +				 * 
        + */ + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + _separators = separators; + _objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; + return this; + } + + }; + prettyPrinter = prettyPrinter.withObjectIndenter(new DefaultIndenter(" ", "\n")); + + myJsonGenerator.setPrettyPrinter(prettyPrinter); + } + return this; + } + + @Override + public JsonLikeWriter flush() { + return this; + } + + @Override + public void close() throws IOException { + myJsonGenerator.close(); + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + myJsonGenerator.writeStartObject(); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + myJsonGenerator.writeObjectFieldStart(name); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + myJsonGenerator.writeArrayFieldStart(name); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + myJsonGenerator.writeObject(value); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + myJsonGenerator.writeNull(); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + myJsonGenerator.writeObjectField(name, value); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + myJsonGenerator.writeEndObject(); + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + myJsonGenerator.writeEndArray(); + return this; + } + + @Override + public JsonLikeWriter endBlock() throws IOException { + myJsonGenerator.writeEndObject(); + return this; + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java index ef69d805e90..9e0f5a52a47 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java @@ -48,7 +48,7 @@ import ca.uhn.fhir.model.primitive.IdDt; * Note that for a * server implementation, the {@link #type()} annotation is optional if the * method is defined in a resource provider, since the type is implied. *
      • * To add tag(s) on the server to the given version of the @@ -63,7 +63,7 @@ import ca.uhn.fhir.model.primitive.IdDt; * operation. * Note that for a server implementation, the * {@link #type()} annotation is optional if the method is defined in a resource provider, since the type is implied.
      • * */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java index 1fd25cf7628..7a4a63cd528 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java @@ -46,7 +46,7 @@ import ca.uhn.fhir.model.primitive.IdDt; * Note that for a * server implementation, the {@link #type()} annotation is optional if the * method is defined in a resource provider, since the type is implied. *
      • * To delete tag(s) on the server to the given version of the @@ -59,7 +59,7 @@ import ca.uhn.fhir.model.primitive.IdDt; * to be deleted. * Note that for a server implementation, the * {@link #type()} annotation is optional if the method is defined in a resource provider, since the type is implied.
      • * */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java index 6423c802018..9628034b3f9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java @@ -22,7 +22,14 @@ package ca.uhn.fhir.rest.api; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; public class Constants { @@ -142,6 +149,8 @@ public class Constants { */ public static final String PARAM_BUNDLETYPE = "_bundletype"; public static final String PARAM_FILTER = "_filter"; + public static final String PARAM_CONTAINED = "_contained"; + public static final String PARAM_CONTAINED_TYPE = "_containedType"; public static final String PARAM_CONTENT = "_content"; public static final String PARAM_COUNT = "_count"; public static final String PARAM_DELETE = "_delete"; @@ -252,7 +261,8 @@ public class Constants { *

        */ public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source"; - public static final String CODESYSTEM_VALIDATE_NOT_NEEDED = UUID.randomUUID().toString(); + public static final String PARAM_FHIRPATH = "_fhirpath"; + public static final String PARAM_TYPE = "_type"; static { CHARSET_UTF8 = StandardCharsets.UTF_8; @@ -354,16 +364,7 @@ public class Constants { CORS_ALLWED_METHODS = Collections.unmodifiableSet(corsAllowedMethods); } - public static boolean codeSystemNotNeeded(String theCodeSystem) { - return Constants.CODESYSTEM_VALIDATE_NOT_NEEDED.equals(theCodeSystem); - } - - public static String codeSystemWithDefaultDescription(String theSystem) { - if (codeSystemNotNeeded(theSystem)) { - return "(none)"; - } else { - return theSystem; - } + return defaultIfBlank(theSystem, "(none)"); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/DeleteCascadeModeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/DeleteCascadeModeEnum.java new file mode 100644 index 00000000000..ee0bf6ab966 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/DeleteCascadeModeEnum.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.rest.api; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2020 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% + */ + +/** + * Used by the client to indicate the cascade mode associated with a delete operation. + *

        + * Note that this is a HAPI FHIR specific feature, and may not work on other platforms. + *

        + */ +public enum DeleteCascadeModeEnum { + + NONE, + + DELETE + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IClientInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IClientInterceptor.java index b44cf49d150..e38527d88d6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IClientInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IClientInterceptor.java @@ -29,7 +29,7 @@ import java.io.IOException; * This interface represents an interceptor which can be used to access (and optionally change or take actions upon) * requests that are being sent by the HTTP client, and responses received by it. *

        - * See the HAPI Documentation Client Interceptor + * See the HAPI Documentation Client Interceptor * page for more information on how to use this feature. *

        */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java index 417c467e3b5..62389f6c5b3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java @@ -63,6 +63,11 @@ public interface IHttpRequest { */ String getUri(); + /** + * Modify the request URI, or null + */ + void setUri(String theUrl); + /** * Return the HTTP verb (e.g. "GET") */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java index 7a0d36ccb3f..50c228a2e41 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java @@ -137,7 +137,7 @@ public class DateClientParam extends BaseClientParam implements IParam { myPrefix = thePrefix; this.previous = previous; } - + public DateWithPrefix(ParamPrefixEnum thePrefix) { myPrefix = thePrefix; } @@ -176,13 +176,27 @@ public class DateClientParam extends BaseClientParam implements IParam { dt.setPrecision(TemporalPrecisionEnum.SECOND); return constructCriterion(dt); } - + + @Override + public IDateCriterion millis(Date theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.MILLI); + return constructCriterion(dt); + } + + @Override + public IDateCriterion millis(String theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.MILLI); + return constructCriterion(dt); + } + private IDateCriterion constructCriterion(DateTimeDt dt) { String valueAsString = dt.getValueAsString(); Criterion criterion = new Criterion(myPrefix, valueAsString); if (previous != null) { criterion.orCriterion = previous; - } + } return criterion; } } @@ -199,8 +213,12 @@ public class DateClientParam extends BaseClientParam implements IParam { IDateCriterion second(String theValue); + IDateCriterion millis(Date theValue); + + IDateCriterion millis(String theValue); + } - + public interface IDateCriterion extends ICriterion { IDateSpecifier orAfter(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java index ad467295203..185a0465a8f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java @@ -34,7 +34,14 @@ public interface IBaseOn { * Perform the operation across all versions of all resources of the given type on the server */ T onType(Class theResourceType); - + + /** + * Perform the operation across all versions of all resources of the given type on the server + * + * @param theResourceType The resource type name, e.g. "ValueSet" + */ + T onType(String theResourceType); + /** * Perform the operation across all versions of a specific resource (by ID and type) on the server. * Note that theId must be populated with both a resource type and a resource ID at diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java index ba1c9c26bbd..898011c6819 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java @@ -35,7 +35,7 @@ public interface IClientExecutable, Y> { * If set to true, the client will log the request and response to the SLF4J logger. This can be useful for * debugging, but is generally not desirable in a production situation. * - * @deprecated Use the client logging interceptor to log requests and responses instead. See here for more information. + * @deprecated Use the client logging interceptor to log requests and responses instead. See here for more information. */ @Deprecated T andLogRequestAndResponse(boolean theLogRequestAndResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteTyped.java index 049b1fef51c..0d05d0701e8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteTyped.java @@ -20,10 +20,15 @@ package ca.uhn.fhir.rest.gclient; * #L% */ +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -public interface IDeleteTyped extends IClientExecutable { - - // nothing for now +public interface IDeleteTyped extends IClientExecutable { + + /** + * Delete cascade mode - Note that this is a HAPI FHIR specific feature and is not supported on all servers. + */ + IDeleteTyped cascade(DeleteCascadeModeEnum theDelete); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index 73f2f1ced75..89e4a4e9041 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -72,6 +72,7 @@ public interface IQuery extends IBaseQuery>, IClientExecutable limitTo(int theLimitTo); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java index a58374f28f4..a54819d8a61 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java @@ -29,10 +29,7 @@ public interface IReadExecutable extends IClientExecuta * that the server return an "HTTP 301 Not Modified" if the newest version of the resource * on the server has the same version as the version ID specified by theVersion. * In this case, the client operation will perform the linked operation. - *

        - * See the ETag Documentation - * for more information. - *

        + * * @param theVersion The version ID (e.g. "123") */ IReadIfNoneMatch ifVersionMatches(String theVersion); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index 5407ac8bd14..086c6d9e624 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -118,6 +118,19 @@ public class ParameterUtil { return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); } + /** + * Removes :modifiers and .chains from URL parameter names + */ + public static String stripModifierPart(String theParam) { + for (int i = 0; i < theParam.length(); i++) { + char nextChar = theParam.charAt(i); + if (nextChar == ':' || nextChar == '.') { + return theParam.substring(0, i); + } + } + return theParam; + } + /** * Escapes a string according to the rules for parameter escaping specified in the FHIR Specification Escaping * Section diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java index 6f9e3babc32..ba47ff0be47 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java @@ -22,16 +22,17 @@ package ca.uhn.fhir.rest.param; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.util.CoverageIgnore; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import java.math.BigDecimal; import static ca.uhn.fhir.model.primitive.IdDt.isValidLong; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ { @@ -78,6 +79,17 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ setValueAsQueryToken(null, null, qualifier, theValue); } + /** + * Constructor + * + * @since 5.0.0 + */ + public ReferenceParam(IIdType theValue) { + if (theValue != null) { + setValueAsQueryToken(null, null, null, theValue.getValue()); + } + } + @Override String doGetQueryParameterQualifier() { StringBuilder b = new StringBuilder(); @@ -200,9 +212,16 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ return myValue; } + /** + * Note that the parameter to this method must be a resource reference, e.g + * 123 or Patient/123 or http://example.com/fhir/Patient/123 + * or something like this. This is not appropriate for cases where a chain is being used and + * the value is for a different type of parameter (e.g. a token). In that case, use one of the + * setter constructors. + */ public ReferenceParam setValue(String theValue) { IdDt id = new IdDt(theValue); - String qualifier= null; + String qualifier = null; if (id.hasResourceType()) { qualifier = ":" + id.getResourceType(); } @@ -224,7 +243,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ * to {@link DateParam}. This is useful if you are using reference parameters and want to handle * chained parameters of different types in a single method. *

        - * See Dynamic Chains + * See Dynamic Chains * in the HAPI FHIR documentation for an example of how to use this method. *

        */ @@ -239,7 +258,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ * to {@link NumberParam}. This is useful if you are using reference parameters and want to handle * chained parameters of different types in a single method. *

        - * See Dynamic Chains + * See Dynamic Chains * in the HAPI FHIR documentation for an example of how to use this method. *

        */ @@ -254,7 +273,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ * to {@link QuantityParam}. This is useful if you are using reference parameters and want to handle * chained parameters of different types in a single method. *

        - * See Dynamic Chains + * See Dynamic Chains * in the HAPI FHIR documentation for an example of how to use this method. *

        */ @@ -279,7 +298,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ * to {@link StringParam}. This is useful if you are using reference parameters and want to handle * chained parameters of different types in a single method. *

        - * See Dynamic Chains + * See Dynamic Chains * in the HAPI FHIR documentation for an example of how to use this method. *

        */ @@ -294,7 +313,7 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/ * to {@link TokenParam}. This is useful if you are using reference parameters and want to handle * chained parameters of different types in a single method. *

        - * See Dynamic Chains + * See Dynamic Chains * in the HAPI FHIR documentation for an example of how to use this method. *

        */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index f4db4b682c2..a28197931be 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -30,6 +30,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.function.Function; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; /** * Utilities for dealing with parameters resources in a version indepenedent way @@ -37,12 +40,26 @@ import java.util.Optional; public class ParametersUtil { public static List getNamedParameterValuesAsString(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { + Function, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null); + return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); + } + + public static List getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { + Function, Integer> mapper = t -> (Integer)t.getValue(); + return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); + } + + public static Optional getNamedParameterValueAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { + return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst(); + } + + private static List extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function, T> theMapper) { Validate.notNull(theParameters, "theParameters must not be null"); RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass()); BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter"); List parameterReps = parameterChild.getAccessor().getValues(theParameters); - List retVal = new ArrayList<>(); + List retVal = new ArrayList<>(); for (IBase nextParameter : parameterReps) { BaseRuntimeElementCompositeDefinition nextParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(nextParameter.getClass()); @@ -62,12 +79,12 @@ public class ParametersUtil { valueValues .stream() .filter(t -> t instanceof IPrimitiveType) - .map(t -> ((IPrimitiveType) t).getValueAsString()) - .filter(StringUtils::isNotBlank) + .map(t->((IPrimitiveType) t)) + .map(theMapper) + .filter(t -> t != null) .forEach(retVal::add); } - return retVal; } @@ -238,7 +255,7 @@ public class ParametersUtil { addPart(theContext, theParameter, theName, coding); } - private static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) { + public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) { BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theParameter.getClass()); BaseRuntimeChildDefinition partChild = def.getChildByName("part"); @@ -252,4 +269,19 @@ public class ParametersUtil { partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue); } + + public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) { + BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theParameter.getClass()); + BaseRuntimeChildDefinition partChild = def.getChildByName("part"); + + BaseRuntimeElementCompositeDefinition partChildElem = (BaseRuntimeElementCompositeDefinition) partChild.getChildByName("part"); + IBase part = partChildElem.newInstance(); + partChild.getMutator().addValue(theParameter, part); + + IPrimitiveType name = (IPrimitiveType) theContext.getElementDefinition("string").newInstance(); + name.setValue(theName); + partChildElem.getChildByName("name").getMutator().addValue(part, name); + + partChildElem.getChildByName("resource").getMutator().addValue(part, theValue); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java index 7573d38bc54..df364b9f143 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java @@ -19,37 +19,75 @@ package ca.uhn.fhir.util; * limitations under the License. * #L% */ -import java.lang.reflect.*; + +import ca.uhn.fhir.context.ConfigurationException; +import org.apache.commons.lang3.Validate; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import org.apache.commons.lang3.Validate; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.support.IContextValidationSupport; - public class ReflectionUtil { - private static final ConcurrentHashMap ourFhirServerVersions = new ConcurrentHashMap(); - + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private static final ConcurrentHashMap ourFhirServerVersions = new ConcurrentHashMap<>(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReflectionUtil.class); - public static LinkedHashSet getDeclaredMethods(Class theClazz) { - LinkedHashSet retVal = new LinkedHashSet(); - for (Method next : theClazz.getDeclaredMethods()) { + /** + * Returns all methods declared against {@literal theClazz}. This method returns a predictable order, which is + * sorted by method name and then by parameters. + */ + public static List getDeclaredMethods(Class theClazz) { + HashSet foundMethods = new LinkedHashSet<>(); + Method[] declaredMethods = theClazz.getDeclaredMethods(); + for (Method next : declaredMethods) { try { Method method = theClazz.getMethod(next.getName(), next.getParameterTypes()); - retVal.add(method); - } catch (NoSuchMethodException e) { - retVal.add(next); - } catch (SecurityException e) { - retVal.add(next); + foundMethods.add(method); + } catch (NoSuchMethodException | SecurityException e) { + foundMethods.add(next); } } + + List retVal = new ArrayList<>(foundMethods); + retVal.sort((Comparator.comparing(ReflectionUtil::describeMethodInSortFriendlyWay))); return retVal; } + /** + * Returns a description like startsWith params(java.lang.String, int) returns(boolean). + * The format is chosen in order to provide a predictable and useful sorting order. + */ + public static String describeMethodInSortFriendlyWay(Method theMethod) { + StringBuilder b = new StringBuilder(); + b.append(theMethod.getName()); + b.append(" returns("); + b.append(theMethod.getReturnType().getName()); + b.append(") params("); + Class[] parameterTypes = theMethod.getParameterTypes(); + for (int i = 0, parameterTypesLength = parameterTypes.length; i < parameterTypesLength; i++) { + if (i > 0) { + b.append(", "); + } + Class next = parameterTypes[i]; + b.append(next.getName()); + } + b.append(")"); + return b.toString(); + } + public static Class getGenericCollectionTypeOfField(Field next) { ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); @@ -93,7 +131,7 @@ public class ReflectionUtil { return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); } - @SuppressWarnings({ "rawtypes" }) + @SuppressWarnings({"rawtypes"}) private static Class getGenericCollectionTypeOf(Type theType) { Class type; if (ParameterizedType.class.isAssignableFrom(theType.getClass())) { @@ -140,43 +178,38 @@ public class ReflectionUtil { public static Object newInstanceOfFhirServerType(String theType) { String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!"; String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer"; - return newInstanceOfType(theType, errorMessage, wantedType); + return newInstanceOfType(theType, theType, errorMessage, wantedType, new Class[0], new Object[0]); } - @SuppressWarnings("unchecked") - public static ca.uhn.fhir.context.support.IContextValidationSupport newInstanceOfFhirProfileValidationSupport( - String theType) { - String errorMessage = "Unable to instantiate validation support! Please make sure that hapi-fhir-validation and the appropriate structures JAR are on your classpath!"; - String wantedType = "ca.uhn.fhir.context.support.IContextValidationSupport"; - Object fhirServerVersion = newInstanceOfType(theType, errorMessage, wantedType); - return (IContextValidationSupport) fhirServerVersion; - } - - private static Object newInstanceOfType(String theType, String errorMessage, String wantedType) { - Object fhirServerVersion = ourFhirServerVersions.get(theType); + private static Object newInstanceOfType(String theKey, String theType, String errorMessage, String wantedType, Class[] theParameterArgTypes, Object[] theConstructorArgs) { + Object fhirServerVersion = ourFhirServerVersions.get(theKey); if (fhirServerVersion == null) { try { Class type = Class.forName(theType); Class serverType = Class.forName(wantedType); Validate.isTrue(serverType.isAssignableFrom(type)); - fhirServerVersion = type.newInstance(); + fhirServerVersion = type.getConstructor(theParameterArgTypes).newInstance(theConstructorArgs); } catch (Exception e) { throw new ConfigurationException(errorMessage, e); } - ourFhirServerVersions.put(theType, fhirServerVersion); + ourFhirServerVersions.put(theKey, fhirServerVersion); } return fhirServerVersion; } - @SuppressWarnings("unchecked") public static T newInstanceOrReturnNull(String theClassName, Class theType) { + return newInstanceOrReturnNull(theClassName, theType, EMPTY_CLASS_ARRAY, EMPTY_OBJECT_ARRAY); + } + + @SuppressWarnings("unchecked") + public static T newInstanceOrReturnNull(String theClassName, Class theType, Class[] theArgTypes, Object[] theArgs) { try { Class clazz = Class.forName(theClassName); if (!theType.isAssignableFrom(clazz)) { throw new ConfigurationException(theClassName + " is not assignable to " + theType); } - return (T) clazz.newInstance(); + return (T) clazz.getConstructor(theArgTypes).newInstance(theArgs); } catch (ConfigurationException e) { throw e; } catch (Exception e) { @@ -185,4 +218,12 @@ public class ReflectionUtil { } } + public static boolean typeExists(String theName) { + try { + Class.forName(theName); + return true; + } catch (ClassNotFoundException theE) { + return false; + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java index a63b4fe1e47..19f7d26b434 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java @@ -112,7 +112,8 @@ public class TestUtil { * environment */ public static void randomizeLocale() { - Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN}; +// Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN}; + Locale[] availableLocales = {Locale.US}; Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]); ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName()); if (Math.random() < 0.5) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java index 99d8ccd7939..5c6b7b9e4ea 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java @@ -59,7 +59,8 @@ public enum VersionEnum { V4_0_3, V4_1_0, V4_2_0, - V4_3_0; + V4_3_0, // 4.3.0 was renamed to 5.0.0 during the cycle + V5_0_0; public static VersionEnum latestVersion() { VersionEnum[] values = VersionEnum.values(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/VersionIndependentConcept.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionIndependentConcept.java similarity index 86% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/VersionIndependentConcept.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionIndependentConcept.java index b8cf3837af7..abd9643c8e7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/VersionIndependentConcept.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionIndependentConcept.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.util; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.term; * #L% */ +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -28,20 +29,30 @@ public class VersionIndependentConcept implements ComparableValidation + * See Validation * for a list of available modules. You may also create your own. */ public interface IValidatorModule { diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 16ba7a08737..605bed74a59 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -36,6 +36,8 @@ ca.uhn.fhir.rest.server.method.IncludeParameter.orIncludeInRequest='OR' query pa ca.uhn.fhir.rest.server.method.PageMethodBinding.unknownSearchId=Search ID "{0}" does not exist and may have expired +ca.uhn.fhir.rest.server.method.ReadMethodBinding.invalidParamsInRequest=Invalid query parameter(s) for this request: "{0}" + ca.uhn.fhir.rest.server.method.SearchMethodBinding.invalidSpecialParamName=Method [{0}] in provider [{1}] contains search parameter annotated to use name [{2}] - This name is reserved according to the FHIR specification and can not be used as a search parameter name. ca.uhn.fhir.rest.server.method.SearchMethodBinding.idWithoutCompartment=Method [{0}] in provider [{1}] has an @IdParam parameter. This is only allowable for compartment search (e.g. @Search(compartment="foo") ) ca.uhn.fhir.rest.server.method.SearchMethodBinding.idNullForCompartmentSearch=ID parameter can not be null or empty for compartment search @@ -81,8 +83,10 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplica ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}" ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided. ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1} +ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.noSystemOrTypeHistoryForPartitionAwareServer=Type- and Server- level history operation not supported on partitioned server ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.cantValidateWithNoResource=No resource supplied for $validate operation (resource is required unless mode is \"delete\") +ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.deleteBlockedBecauseDisabled=Resource deletion is not permitted on this server ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithInvalidId=Can not process entity with ID[{0}], this is not a valid FHIR ID ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1} @@ -134,3 +138,25 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can no ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} + +ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1} + +ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned +ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionId=Unknown partition ID: {0} +ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionName=Unknown partition name: {0} + + +ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1} +ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}" + +ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request + +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.missingPartitionIdOrName=Partition must have an ID and a Name +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreatePartition0=Can not create a partition with ID 0 (this is a reserved value) +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.unknownPartitionId=No partition exists with ID {0} +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.invalidName=Partition name "{0}" is not valid +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantCreateDuplicatePartitionName=Partition name "{0}" is already defined +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantDeleteDefaultPartition=Can not delete default partition +ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantRenameDefaultPartition=Can not rename default partition + +ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java index bc120a0e2c6..3b71f8a8840 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/json/JsonLikeStructureTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; import java.io.StringReader; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; import org.junit.Test; public class JsonLikeStructureTest { @@ -39,7 +40,7 @@ public class JsonLikeStructureTest { @Test public void testStructureLoading() { StringReader reader = new StringReader(TEST_STRUCTURELOADING_DATA); - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(reader); JsonLikeObject rootObject = jsonStructure.getRootObject(); @@ -70,7 +71,7 @@ public class JsonLikeStructureTest { @Test public void testJsonAndDataTypes() { StringReader reader = new StringReader(TEST_JSONTYPES_DATA); - JsonLikeStructure jsonStructure = new GsonStructure(); + JsonLikeStructure jsonStructure = new JacksonStructure(); jsonStructure.load(reader); JsonLikeObject rootObject = jsonStructure.getRootObject(); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/QualifierDetailsTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/QualifierDetailsTest.java new file mode 100644 index 00000000000..d63b545b74e --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/QualifierDetailsTest.java @@ -0,0 +1,21 @@ +package ca.uhn.fhir.rest.param; + +import com.google.common.collect.Sets; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class QualifierDetailsTest { + + @Test + public void testBlacklist() { + + QualifierDetails details = new QualifierDetails(); + details.setColonQualifier(":Patient"); + assertFalse(details.passes(null, Sets.newHashSet(":Patient"))); + assertTrue(details.passes(null, Sets.newHashSet(":Observation"))); + + } + + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ReflectionUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ReflectionUtilTest.java index 5742f042135..3239dcc9420 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ReflectionUtilTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ReflectionUtilTest.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.util; import static org.junit.Assert.*; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -45,4 +46,18 @@ public class ReflectionUtilTest { assertEquals("Failed to instantiate java.util.List", e.getMessage()); } } + + @Test + public void testTypeExists() { + assertFalse(ReflectionUtil.typeExists("ca.Foo")); + assertTrue(ReflectionUtil.typeExists(String.class.getName())); + } + + @Test + public void testDescribeMethod() throws NoSuchMethodException { + Method method = String.class.getMethod("startsWith", String.class, int.class); + String description = ReflectionUtil.describeMethodInSortFriendlyWay(method); + assertEquals("startsWith returns(boolean) params(java.lang.String, int)", description); + } + } diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 450e56c6aa0..2ea38dd0165 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -3,14 +3,14 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT pom HAPI FHIR BOM ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index 6d56d743525..00886f64918 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java index 33c36cc8bdb..c1b98297f41 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java @@ -25,7 +25,11 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import com.helger.commons.io.file.FileHelper; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.WordUtils; import org.fusesource.jansi.Ansi; @@ -43,11 +47,12 @@ import static org.fusesource.jansi.Ansi.ansi; @SuppressWarnings("WeakerAccess") public abstract class BaseApp { + protected static final org.slf4j.Logger ourLog; + static final String LINESEP = System.getProperty("line.separator"); private static final String STACKFILTER_PATTERN = "%xEx{full, sun.reflect, org.junit, org.eclipse, java.lang.reflect.Method, org.springframework, org.hibernate, com.sun.proxy, org.attoparser, org.thymeleaf}"; private static final String STACKFILTER_PATTERN_PROP = "log.stackfilter.pattern"; - static final String LINESEP = System.getProperty("line.separator"); - protected static final org.slf4j.Logger ourLog; private static List ourCommands; + private static boolean ourDebugMode; static { System.setProperty(STACKFILTER_PATTERN_PROP, STACKFILTER_PATTERN); @@ -115,13 +120,19 @@ public abstract class BaseApp { System.out.println("Options:"); HelpFormatter fmt = new HelpFormatter(); PrintWriter pw = new PrintWriter(System.out); - fmt.printOptions(pw, columns, theCommand.getOptions(), 2, 2); + fmt.printOptions(pw, columns, getOptions(theCommand), 2, 2); pw.flush(); // That's it! System.out.println(); } + private Options getOptions(BaseCommand theCommand) { + Options options = theCommand.getOptions(); + options.addOption(null, "debug", false, "Enable debug mode"); + return options; + } + private void logUsage() { logAppHeader(); System.out.println("Usage:"); @@ -232,7 +243,7 @@ public abstract class BaseApp { myShutdownHook = new MyShutdownHook(command); Runtime.getRuntime().addShutdownHook(myShutdownHook); - Options options = command.getOptions(); + Options options = getOptions(command); DefaultParser parser = new DefaultParser(); CommandLine parsedOptions; @@ -247,6 +258,11 @@ public abstract class BaseApp { throw new ParseException("Unrecognized argument: " + parsedOptions.getArgList().get(0)); } + if (parsedOptions.hasOption("debug")) { + loggingConfigOnDebug(); + ourDebugMode = true; + } + // Actually execute the command command.run(parsedOptions); @@ -290,7 +306,7 @@ public abstract class BaseApp { private void exitDueToException(Throwable e) { if ("true".equals(System.getProperty("test"))) { if (e instanceof CommandFailureException) { - throw (CommandFailureException)e; + throw (CommandFailureException) e; } throw new Error(e); } else { @@ -316,6 +332,24 @@ public abstract class BaseApp { } } + private class MyShutdownHook extends Thread { + private final BaseCommand myFinalCommand; + + MyShutdownHook(BaseCommand theFinalCommand) { + myFinalCommand = theFinalCommand; + } + + @Override + public void run() { + ourLog.info(provideProductName() + " is shutting down..."); + myFinalCommand.cleanup(); + } + } + + public static boolean isDebugMode() { + return ourDebugMode; + } + private static void loggingConfigOff() { try { JoranConfigurator configurator = new JoranConfigurator(); @@ -337,18 +371,16 @@ public abstract class BaseApp { } } - - private class MyShutdownHook extends Thread { - private final BaseCommand myFinalCommand; - - MyShutdownHook(BaseCommand theFinalCommand) { - myFinalCommand = theFinalCommand; + private static void loggingConfigOnDebug() { + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); + ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); + configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on-debug.xml")); + } catch (JoranException e) { + e.printStackTrace(); } - @Override - public void run() { - ourLog.info(provideProductName() + " is shutting down..."); - myFinalCommand.cleanup(); - } + ourLog.info("Debug logging is enabled"); } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java index 4f21a02acc8..89a9db47432 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java @@ -44,8 +44,8 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; @@ -54,9 +54,18 @@ import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -152,7 +161,7 @@ public class ExampleDataUploader extends BaseCommand { bundle.setType(BundleType.TRANSACTION); FhirValidator val = ctx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(new DefaultProfileValidationSupport())); + val.registerValidatorModule(new FhirInstanceValidator(new DefaultProfileValidationSupport(ctx))); ZipInputStream zis = new ZipInputStream(FileUtils.openInputStream(inputFile)); byte[] buffer = new byte[2048]; @@ -236,7 +245,7 @@ public class ExampleDataUploader extends BaseCommand { bundle.setType(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION); FhirValidator val = ctx.newValidator(); - val.registerValidatorModule(new org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator(new org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport())); + val.registerValidatorModule(new FhirInstanceValidator(new DefaultProfileValidationSupport(ctx))); ZipInputStream zis = new ZipInputStream(FileUtils.openInputStream(inputFile)); byte[] buffer = new byte[2048]; @@ -615,7 +624,6 @@ public class ExampleDataUploader extends BaseCommand { } String specUrl; - switch (ctx.getVersion().getVersion()) { case DSTU2: specUrl = "http://hl7.org/fhir/dstu2/examples-json.zip"; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java index bcb2db0aa2d..17ca4c9b26c 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java @@ -21,13 +21,13 @@ package ca.uhn.fhir.cli; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3; import ca.uhn.fhir.rest.client.api.IGenericClient; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -96,7 +96,7 @@ public class IgPackUploader extends BaseCommand { } catch (FileNotFoundException e) { throw new CommandFailureException(e); } - Iterable conformanceResources = ig.fetchAllConformanceResources(ctx); + Iterable conformanceResources = ig.fetchAllConformanceResources(); for (IBaseResource nextResource : conformanceResources) { String nextResourceUrl = ((IPrimitiveType)ctx.newTerser().getSingleValueOrNull(nextResource, "url")).getValueAsString(); ourLog.info("Uploading resource: {}", nextResourceUrl); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java index bd349c37261..52b9df7659a 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java @@ -20,20 +20,12 @@ package ca.uhn.fhir.cli; * #L% */ -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; - -import java.util.ArrayList; -import java.util.List; +import org.hl7.fhir.instance.model.api.IBaseResource; public class LoadingValidationSupportDstu2 implements IValidationSupport { @@ -43,22 +35,7 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu2.class); @Override - public List allStructures() { - return new ArrayList<>(); - } - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + public T fetchResource(Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); @@ -76,13 +53,13 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport { } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { return false; } @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - return null; + public FhirContext getFhirContext() { + return myCtx; } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java index f1bf0087e6b..25847c89348 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java @@ -21,20 +21,12 @@ package ca.uhn.fhir.cli; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import java.util.Collections; -import java.util.List; - public class LoadingValidationSupportDstu3 implements IValidationSupport { private FhirContext myCtx = FhirContext.forDstu3(); @@ -43,27 +35,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu3.class); @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + public T fetchResource(Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); @@ -81,33 +53,8 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport { } @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); + public FhirContext getFhirContext() { + return myCtx; } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java index a1204ff2ebc..4416770676c 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java @@ -21,51 +21,19 @@ package ca.uhn.fhir.cli; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import java.util.Collections; -import java.util.List; - -public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IValidationSupport { +public class LoadingValidationSupportR4 implements IValidationSupport { // TODO: Don't use qualified names for loggers in HAPI CLI. private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportR4.class); private FhirContext myCtx = FhirContext.forR4(); @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + public T fetchResource(Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); @@ -83,28 +51,9 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal } @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; + public FhirContext getFhirContext() { + return myCtx; } - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/RunServerCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/RunServerCommand.java index 0ad0a398ca8..bcddfccc168 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/RunServerCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/RunServerCommand.java @@ -20,13 +20,12 @@ package ca.uhn.fhir.cli; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.demo.ContextHolder; import ca.uhn.fhir.jpa.demo.FhirServerConfig; import ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3; import ca.uhn.fhir.jpa.demo.FhirServerConfigR4; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ValidateCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ValidateCommand.java index 13b610d0f57..efc48664636 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ValidateCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ValidateCommand.java @@ -21,6 +21,8 @@ package ca.uhn.fhir.cli; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu2; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3; import ca.uhn.fhir.parser.DataFormatException; @@ -28,17 +30,18 @@ import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; -import com.helger.commons.io.file.FileHelper; import com.google.common.base.Charsets; -import org.apache.commons.cli.*; +import com.helger.commons.io.file.FileHelper; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.text.WordUtils; import org.fusesource.jansi.Ansi.Color; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IBaseResource; import java.io.File; @@ -46,7 +49,10 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.leftPad; import static org.fusesource.jansi.Ansi.ansi; public class ValidateCommand extends BaseCommand { @@ -151,43 +157,37 @@ public class ValidateCommand extends BaseCommand { if (theCommandLine.hasOption("p")) { switch (ctx.getVersion().getVersion()) { case DSTU2: { - org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator instanceValidator = new org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator(); - val.registerValidatorModule(instanceValidator); - org.hl7.fhir.instance.hapi.validation.ValidationSupportChain validationSupport = new org.hl7.fhir.instance.hapi.validation.ValidationSupportChain( - new org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport()); + ValidationSupportChain validationSupport = new ValidationSupportChain( + new DefaultProfileValidationSupport(ctx), new InMemoryTerminologyServerValidationSupport(ctx)); if (igPack != null) { FhirContext hl7orgCtx = FhirContext.forDstu2Hl7Org(); hl7orgCtx.setParserErrorHandler(new LenientErrorHandler(false)); IgPackParserDstu2 parser = new IgPackParserDstu2(hl7orgCtx); - org.hl7.fhir.instance.hapi.validation.IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename); + IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename); validationSupport.addValidationSupport(igValidationSupport); } - if (localProfileResource != null) { - org.hl7.fhir.dstu2.model.StructureDefinition convertedSd = FhirContext.forDstu2Hl7Org().newXmlParser().parseResource(org.hl7.fhir.dstu2.model.StructureDefinition.class, ctx.newXmlParser().encodeResourceToString(localProfileResource)); - instanceValidator.setStructureDefintion(convertedSd); - } if (theCommandLine.hasOption("r")) { - validationSupport.addValidationSupport(new LoadingValidationSupportDstu2()); + validationSupport.addValidationSupport((IValidationSupport) new LoadingValidationSupportDstu2()); } - instanceValidator.setValidationSupport(validationSupport); + FhirInstanceValidator instanceValidator; + instanceValidator = new FhirInstanceValidator(validationSupport); + val.registerValidatorModule(instanceValidator); + break; } case DSTU3: { - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx); val.registerValidatorModule(instanceValidator); - ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport()); + ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), new InMemoryTerminologyServerValidationSupport(ctx)); if (igPack != null) { IgPackParserDstu3 parser = new IgPackParserDstu3(getFhirContext()); IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename); validationSupport.addValidationSupport(igValidationSupport); } - if (localProfileResource != null) { - instanceValidator.setStructureDefintion((StructureDefinition) localProfileResource); - } if (theCommandLine.hasOption("r")) { - validationSupport.addValidationSupport(new LoadingValidationSupportDstu3()); + validationSupport.addValidationSupport((IValidationSupport) new LoadingValidationSupportDstu3()); } instanceValidator.setValidationSupport(validationSupport); break; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml new file mode 100644 index 00000000000..fa16cfb7451 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml @@ -0,0 +1,53 @@ + + + + false + + %d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n + + + + + output.log + false + + %d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n + + utf-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java index 9550a66ab17..3e86c205e80 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider; import com.google.common.base.Charsets; @@ -88,7 +89,8 @@ public class HashMapResourceProviderConceptMapDstu3 extends HashMapResourceProvi @Update public MethodOutcome update( @ResourceParam ConceptMap theConceptMap, - @ConditionalUrlParam String theConditional) { + @ConditionalUrlParam String theConditional, + RequestDetails theRequestDetails) { MethodOutcome methodOutcome = new MethodOutcome(); @@ -112,14 +114,14 @@ public class HashMapResourceProviderConceptMapDstu3 extends HashMapResourceProvi List conceptMaps = searchByUrl(url); if (!conceptMaps.isEmpty()) { - methodOutcome = super.update(conceptMaps.get(0), null); + methodOutcome = super.update(conceptMaps.get(0), null, theRequestDetails); } else { - methodOutcome = create(theConceptMap); + methodOutcome = create(theConceptMap, theRequestDetails); } } } else { - methodOutcome = super.update(theConceptMap, null); + methodOutcome = super.update(theConceptMap, null, theRequestDetails); } return methodOutcome; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java index 471af74a751..2ae3a4b8a0b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider; import com.google.common.base.Charsets; @@ -88,7 +89,8 @@ public class HashMapResourceProviderConceptMapR4 extends HashMapResourceProvider @Update public MethodOutcome update( @ResourceParam ConceptMap theConceptMap, - @ConditionalUrlParam String theConditional) { + @ConditionalUrlParam String theConditional, + RequestDetails theRequestDetails) { MethodOutcome methodOutcome = new MethodOutcome(); @@ -111,14 +113,14 @@ public class HashMapResourceProviderConceptMapR4 extends HashMapResourceProvider List conceptMaps = searchByUrl(url); if (!conceptMaps.isEmpty()) { - methodOutcome = super.update(conceptMaps.get(0), null); + methodOutcome = super.update(conceptMaps.get(0), null, theRequestDetails); } else { - methodOutcome = create(theConceptMap); + methodOutcome = create(theConceptMap, theRequestDetails); } } } else { - methodOutcome = super.update(theConceptMap, null); + methodOutcome = super.update(theConceptMap, null, theRequestDetails); } return methodOutcome; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateCommandTest.java similarity index 72% rename from hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateTest.java rename to hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateCommandTest.java index 082e3254850..1e6bbd71baa 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ValidateCommandTest.java @@ -6,8 +6,8 @@ import org.junit.Test; import static org.junit.Assert.fail; -public class ValidateTest { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class); +public class ValidateCommandTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCommandTest.class); @Before public void before() { @@ -16,7 +16,7 @@ public class ValidateTest { @Test public void testValidateLocalProfile() { - String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile(); + String resourcePath = ValidateCommandTest.class.getResource("/patient-uslab-example1.xml").getFile(); ourLog.info(resourcePath); App.main(new String[] { @@ -29,7 +29,7 @@ public class ValidateTest { @Test @Ignore public void testValidateUsingIgPackSucceedingDstu2() { - String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-good.json").getFile(); + String resourcePath = ValidateCommandTest.class.getResource("/argo-dstu2-observation-good.json").getFile(); ourLog.info(resourcePath); App.main(new String[] { @@ -42,7 +42,7 @@ public class ValidateTest { @Test public void testValidateUsingIgPackFailingDstu2() { - String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-bad.json").getFile(); + String resourcePath = ValidateCommandTest.class.getResource("/argo-dstu2-observation-bad.json").getFile(); ourLog.info(resourcePath); try { diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index e8b4258a8e5..8a2909e342e 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index 649f92bc336..3c58ce3131d 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../hapi-deployable-pom diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/CommonConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/CommonConfig.java index 396b572ccb9..640d8fa3b87 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/CommonConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/CommonConfig.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.demo; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/ContextHolder.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/ContextHolder.java index cc6d90d990e..7e3eb9fecf1 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/ContextHolder.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/ContextHolder.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.demo; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.Validate; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index ecfcaee81d2..7dc79278c2d 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.demo; */ import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu3.java index dab96ef9505..27c4da9cfed 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu3.java @@ -21,13 +21,10 @@ package ca.uhn.fhir.jpa.demo; */ import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; -import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigR4.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigR4.java index ed0ce328490..8188808c013 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigR4.java @@ -21,12 +21,10 @@ package ca.uhn.fhir.jpa.demo; */ import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; -import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index 6f63fdd5a0e..1c6060e7f91 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -23,10 +23,10 @@ package ca.uhn.fhir.jpa.demo; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; 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.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; @@ -35,8 +35,8 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; -import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; @@ -126,14 +126,14 @@ public class JpaServerDemo extends RestfulServer { IFhirSystemDao systemDao = myAppCtx .getBean("mySystemDaoDstu3", IFhirSystemDao.class); JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); + myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("Example Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.R4) { IFhirSystemDao systemDao = myAppCtx .getBean("mySystemDaoR4", IFhirSystemDao.class); JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); + myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("Example Server"); setServerConformanceProvider(confProvider); } else { @@ -172,12 +172,9 @@ public class JpaServerDemo extends RestfulServer { daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity()); daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis()); - SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); - subscriptionInterceptorLoader.registerInterceptors(); - DaoRegistry daoRegistry = myAppCtx.getBean(DaoRegistry.class); IInterceptorBroadcaster interceptorBroadcaster = myAppCtx.getBean(IInterceptorBroadcaster.class); - CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(daoRegistry, interceptorBroadcaster); + CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, daoRegistry, interceptorBroadcaster); getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); } diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index c3152ce67f4..af4ea7b909a 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 1d01a8cb58e..8e81e188d76 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java index 8ef7d347f77..ce2c7e7d347 100644 --- a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java +++ b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java @@ -89,7 +89,12 @@ public class OkHttpRestfulRequest implements IHttpRequest { return myUrl; } - @Override + @Override + public void setUri(String theUrl) { + myUrl = theUrl; + } + + @Override public String getHttpVerbName() { return myRequestTypeEnum.name(); } diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index f085e293764..d0bebdfca81 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java index fe45528578d..5f9a2334a27 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java @@ -34,6 +34,7 @@ import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ContentType; import java.io.IOException; +import java.net.URI; import java.nio.charset.Charset; import java.util.*; @@ -110,6 +111,11 @@ public class ApacheHttpRequest implements IHttpRequest { return null; } + @Override + public void setUri(String theUrl) { + myRequest.setURI(URI.create(theUrl)); + } + @Override public String getUri() { return myRequest.getURI().toString(); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 9d31bb4ea8a..8aad6fc48d7 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -297,6 +297,7 @@ public abstract class BaseClient implements IRestfulClient { HookParams requestParams = new HookParams(); requestParams.add(IHttpRequest.class, httpRequest); + requestParams.add(IRestfulClient.class, this); getInterceptorService().callHooks(Pointcut.CLIENT_REQUEST, requestParams); response = httpRequest.execute(); @@ -304,6 +305,7 @@ public abstract class BaseClient implements IRestfulClient { HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); + responseParams.add(IRestfulClient.class, this); getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); String mimeType; diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/ClientInvocationHandlerFactory.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/ClientInvocationHandlerFactory.java index 9a93c907d80..f24d97977e2 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/ClientInvocationHandlerFactory.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/ClientInvocationHandlerFactory.java @@ -83,7 +83,7 @@ public class ClientInvocationHandlerFactory { class RegisterInterceptorLambda implements ILambda { @Override public Object handle(ClientInvocationHandler theTarget, Object[] theArgs) { - IClientInterceptor interceptor = (IClientInterceptor) theArgs[0]; + Object interceptor = theArgs[0]; theTarget.registerInterceptor(interceptor); return null; } @@ -130,7 +130,7 @@ public class ClientInvocationHandlerFactory { class UnregisterInterceptorLambda implements ILambda { @Override public Object handle(ClientInvocationHandler theTarget, Object[] theArgs) { - IClientInterceptor interceptor = (IClientInterceptor) theArgs[0]; + Object interceptor = theArgs[0]; theTarget.unregisterInterceptor(interceptor); return null; } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index 40804918554..45918f97c00 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -604,26 +604,41 @@ public class GenericClient extends BaseClient implements IGenericClient { } - private class DeleteInternal extends BaseSearch implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { + private class DeleteInternal extends BaseSearch implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { private boolean myConditional; private IIdType myId; private String myResourceType; private String mySearchUrl; + private DeleteCascadeModeEnum myCascadeMode; @Override - public IBaseOperationOutcome execute() { + public MethodOutcome execute() { + + Map> additionalParams = new HashMap<>(); + if (myCascadeMode != null) { + switch (myCascadeMode) { + case DELETE: + addParam(getParamMap(), Constants.PARAMETER_CASCADE_DELETE, Constants.CASCADE_DELETE); + break; + default: + case NONE: + break; + } + } + HttpDeleteClientInvocation invocation; if (myId != null) { - invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myId); + invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myId, getParamMap()); } else if (myConditional) { invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myResourceType, getParamMap()); } else { - invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), mySearchUrl); + invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), mySearchUrl, getParamMap()); } - OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); - Map> params = new HashMap>(); - return invoke(params, binding, invocation); + + OutcomeResponseHandler binding = new OutcomeResponseHandler(); + + return invoke(additionalParams, binding, invocation); } @Override @@ -687,6 +702,11 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IDeleteTyped cascade(DeleteCascadeModeEnum theDelete) { + myCascadeMode = theDelete; + return this; + } } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -815,6 +835,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IHistoryUntyped onType(String theResourceType) { + myType = myContext.getResourceDefinition(theResourceType).getImplementingClass(); + return this; + } + @Override public IHistoryTyped since(Date theCutoff) { if (theCutoff != null) { @@ -1223,6 +1249,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IOperationUnnamed onType(String theResourceType) { + myType = myContext.getResourceDefinition(theResourceType).getImplementingClass(); + return this; + } + @Override public IOperationProcessMsg processMessage() { myOperationName = Constants.EXTOP_PROCESS_MESSAGE; @@ -1358,30 +1390,6 @@ public class GenericClient extends BaseClient implements IGenericClient { } } - private final class OperationOutcomeResponseHandler implements IClientResponseHandler { - - @Override - public IBaseOperationOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) - throws BaseServerResponseException { - EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); - if (respType == null) { - return null; - } - IParser parser = respType.newParser(myContext); - IBaseOperationOutcome retVal; - try { - // TODO: handle if something else than OO comes back - retVal = (IBaseOperationOutcome) parser.parseResource(theResponseInputStream); - } catch (DataFormatException e) { - ourLog.warn("Failed to parse OperationOutcome response", e); - return null; - } - MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, retVal); - - return retVal; - } - } - private final class OutcomeResponseHandler implements IClientResponseHandler { private PreferReturnEnum myPrefer; diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java index f5ca6a60a4a..c7e51cd082c 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java @@ -35,7 +35,7 @@ import org.apache.commons.lang3.Validate; * HTTP interceptor to be used for adding HTTP basic auth username/password tokens * to requests *

        - * See the HAPI Documentation + * See the HAPI Documentation * for information on how to use this class. *

        */ diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java index 1bcfc174144..a0199be3da3 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java @@ -35,7 +35,7 @@ import ca.uhn.fhir.util.CoverageIgnore; * where the token portion (at the end of the header) is supplied by the invoking code. *

        *

        - * See the HAPI Documentation for information on how to use this class. + * See the HAPI Documentation for information on how to use this class. *

        */ public class BearerTokenAuthInterceptor implements IClientInterceptor { diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java index ce5aa1452cd..abcdeb35835 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java @@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.client.interceptor; * #L% */ -import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -35,7 +37,8 @@ import java.io.IOException; * * @see ThreadLocalCapturingInterceptor for an interceptor that uses a ThreadLocal in order to work in multithreaded environments */ -public class CapturingInterceptor implements IClientInterceptor { +@Interceptor +public class CapturingInterceptor { private IHttpRequest myLastRequest; private IHttpResponse myLastResponse; @@ -56,12 +59,12 @@ public class CapturingInterceptor implements IClientInterceptor { return myLastResponse; } - @Override + @Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.CAPTURING_INTERCEPTOR_REQUEST) public void interceptRequest(IHttpRequest theRequest) { myLastRequest = theRequest; } - @Override + @Hook(value = Pointcut.CLIENT_RESPONSE, order = InterceptorOrders.CAPTURING_INTERCEPTOR_RESPONSE) public void interceptResponse(IHttpResponse theResponse) { //Buffer the reponse to avoid errors when content has already been read and the entity is not repeatable bufferResponse(theResponse); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/InterceptorOrders.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/InterceptorOrders.java new file mode 100644 index 00000000000..e67b5ac20fe --- /dev/null +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/InterceptorOrders.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.rest.client.interceptor; + +/*- + * #%L + * HAPI FHIR - Client Framework + * %% + * Copyright (C) 2014 - 2020 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 InterceptorOrders { + + int LOGGING_INTERCEPTOR_REQUEST = -2; + int URL_TENANT_SELECTION_INTERCEPTOR_REQUEST = 100; + int CAPTURING_INTERCEPTOR_REQUEST = 1000; + + int CAPTURING_INTERCEPTOR_RESPONSE = -1; + int LOGGING_INTERCEPTOR_RESPONSE = 1001; +} diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java index 202d587d6bd..06de843f610 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java @@ -20,6 +20,19 @@ package ca.uhn.fhir.rest.client.interceptor; * #L% */ +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; + import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -27,20 +40,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import ca.uhn.fhir.interceptor.api.Hook; -import ca.uhn.fhir.interceptor.api.Interceptor; -import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.Constants; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.Validate; -import org.slf4j.Logger; - -import ca.uhn.fhir.rest.client.api.IClientInterceptor; -import ca.uhn.fhir.rest.client.api.IHttpRequest; -import ca.uhn.fhir.rest.client.api.IHttpResponse; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; - @Interceptor public class LoggingInterceptor implements IClientInterceptor { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class); @@ -62,9 +61,8 @@ public class LoggingInterceptor implements IClientInterceptor { /** * Constructor for client logging interceptor - * - * @param theVerbose - * If set to true, all logging is enabled + * + * @param theVerbose If set to true, all logging is enabled */ public LoggingInterceptor(boolean theVerbose) { if (theVerbose) { @@ -78,7 +76,7 @@ public class LoggingInterceptor implements IClientInterceptor { } @Override - @Hook(Pointcut.CLIENT_REQUEST) + @Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.LOGGING_INTERCEPTOR_RESPONSE) public void interceptRequest(IHttpRequest theRequest) { if (myLogRequestSummary) { myLog.info("Client request: {}", theRequest); @@ -102,7 +100,7 @@ public class LoggingInterceptor implements IClientInterceptor { } @Override - @Hook(Pointcut.CLIENT_RESPONSE) + @Hook(value = Pointcut.CLIENT_RESPONSE, order = InterceptorOrders.LOGGING_INTERCEPTOR_REQUEST) public void interceptResponse(IHttpResponse theResponse) throws IOException { if (myLogResponseSummary) { String message = "HTTP " + theResponse.getStatus() + " " + theResponse.getStatusInfo(); @@ -167,18 +165,18 @@ public class LoggingInterceptor implements IClientInterceptor { StringBuilder b = new StringBuilder(); if (theHeaders != null && !theHeaders.isEmpty()) { Iterator nameEntries = theHeaders.keySet().iterator(); - while(nameEntries.hasNext()) { + while (nameEntries.hasNext()) { String key = nameEntries.next(); Iterator values = theHeaders.get(key).iterator(); - while(values.hasNext()) { + while (values.hasNext()) { String value = values.next(); - b.append(key); - b.append(": "); - b.append(value); - if (nameEntries.hasNext() || values.hasNext()) { - b.append('\n'); - } + b.append(key); + b.append(": "); + b.append(value); + if (nameEntries.hasNext() || values.hasNext()) { + b.append('\n'); } + } } } return b; @@ -187,9 +185,8 @@ public class LoggingInterceptor implements IClientInterceptor { /** * Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect * logs to a differently named logger instead. - * - * @param theLogger - * The logger to use. Must not be null. + * + * @param theLogger The logger to use. Must not be null. */ public void setLogger(Logger theLogger) { Validate.notNull(theLogger, "theLogger can not be null"); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java new file mode 100644 index 00000000000..26e0bf5c36c --- /dev/null +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java @@ -0,0 +1,88 @@ +package ca.uhn.fhir.rest.client.interceptor; + +/*- + * #%L + * HAPI FHIR - Client Framework + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IRestfulClient; +import org.apache.commons.lang3.Validate; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * This interceptor adds a path element representing the tenant ID to each client request. It is primarily + * intended to be used with clients that are accessing servers using + * URL Base Multitenancy. + */ +public class UrlTenantSelectionInterceptor { + + private String myTenantId; + + /** + * Constructor + */ + public UrlTenantSelectionInterceptor() { + this(null); + } + + /** + * Constructor + * + * @param theTenantId The tenant ID to add to URL base + */ + public UrlTenantSelectionInterceptor(String theTenantId) { + myTenantId = theTenantId; + } + + /** + * Returns the tenant ID + */ + public String getTenantId() { + return myTenantId; + } + + /** + * Sets the tenant ID + */ + public void setTenantId(String theTenantId) { + myTenantId = theTenantId; + } + + @Hook(value = Pointcut.CLIENT_REQUEST, order = InterceptorOrders.URL_TENANT_SELECTION_INTERCEPTOR_REQUEST) + public void request(IRestfulClient theClient, IHttpRequest theRequest) { + String tenantId = getTenantId(); + if (isBlank(tenantId)) { + return; + } + String requestUri = theRequest.getUri(); + String serverBase = theClient.getServerBase(); + if (serverBase.endsWith("/")) { + serverBase = serverBase.substring(0, serverBase.length() - 1); + } + + Validate.isTrue(requestUri.startsWith(serverBase), "Request URI %s does not start with server base %s", requestUri, serverBase); + + String newUri = serverBase + "/" + tenantId + requestUri.substring(serverBase.length()); + theRequest.setUri(newUri); + } + +} diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java index 00fb4112dd0..46391030628 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java @@ -65,18 +65,18 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithRe @Override public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - IIdType idDt = (IIdType) theArgs[getIdParameterIndex()]; - if (idDt == null) { + IIdType id = (IIdType) theArgs[getIdParameterIndex()]; + if (id == null) { throw new NullPointerException("ID can not be null"); } - if (idDt.hasResourceType() == false) { - idDt = idDt.withResourceType(getResourceName()); - } else if (getResourceName().equals(idDt.getResourceType()) == false) { - throw new InvalidRequestException("ID parameter has the wrong resource type, expected '" + getResourceName() + "', found: " + idDt.getResourceType()); + if (id.hasResourceType() == false) { + id = id.withResourceType(getResourceName()); + } else if (getResourceName().equals(id.getResourceType()) == false) { + throw new InvalidRequestException("ID parameter has the wrong resource type, expected '" + getResourceName() + "', found: " + id.getResourceType()); } - HttpDeleteClientInvocation retVal = createDeleteInvocation(getContext(), idDt); + HttpDeleteClientInvocation retVal = createDeleteInvocation(getContext(), id, Collections.emptyMap()); for (int idx = 0; idx < theArgs.length; idx++) { IParameter nextParam = getParameters().get(idx); @@ -86,9 +86,8 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithRe return retVal; } - public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, IIdType theId) { - HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theContext, theId); - return retVal; + public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, IIdType theId, Map> theAdditionalParams) { + return new HttpDeleteClientInvocation(theContext, theId, theAdditionalParams); } @@ -97,13 +96,8 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithRe return null; } - public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, String theSearchUrl) { - HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theContext, theSearchUrl); - return retVal; - } - - public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, String theResourceType, Map> theParams) { - return new HttpDeleteClientInvocation(theContext, theResourceType, theParams); + public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, String theSearchUrl, Map> theParams) { + return new HttpDeleteClientInvocation(theContext, theSearchUrl, theParams); } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpDeleteClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpDeleteClientInvocation.java index 911856bdb9b..1d5c49e361d 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpDeleteClientInvocation.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpDeleteClientInvocation.java @@ -36,19 +36,15 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { private String myUrlPath; private Map> myParams; - public HttpDeleteClientInvocation(FhirContext theContext, IIdType theId) { + public HttpDeleteClientInvocation(FhirContext theContext, IIdType theId, Map> theAdditionalParams) { super(theContext); myUrlPath = theId.toUnqualifiedVersionless().getValue(); + myParams = theAdditionalParams; } - public HttpDeleteClientInvocation(FhirContext theContext, String theSearchUrl) { + public HttpDeleteClientInvocation(FhirContext theContext, String theSearchUrl, Map> theParams) { super(theContext); myUrlPath = theSearchUrl; - } - - public HttpDeleteClientInvocation(FhirContext theContext, String theResourceType, Map> theParams) { - super(theContext); - myUrlPath = theResourceType; myParams = theParams; } @@ -67,10 +63,4 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { return createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.DELETE); } - @Override - protected IHttpRequest createHttpRequest(String theUrl, EncodingEnum theEncoding, RequestTypeEnum theRequestType) { - // TODO Auto-generated method stub - return super.createHttpRequest(theUrl, theEncoding, theRequestType); - } - } diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index c346272f57d..63dd461e26e 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -67,6 +67,12 @@ ${project.version} true + + ca.uhn.hapi.fhir + hapi-fhir-structures-r5 + ${project.version} + true + ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 @@ -85,6 +91,16 @@ Saxon-HE + + + com.google.code.gson + gson + true + + ch.qos.logback diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index ec6e0a219ca..2496d5582e6 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml @@ -271,7 +271,6 @@ false ${project.basedir}/src/assembly/hapi-fhir-standard-distribution.xml - ${project.basedir}/src/assembly/hapi-fhir-jpaserver-example.xml ${project.basedir}/src/assembly/hapi-fhir-android-distribution.xml ${project.basedir}/src/assembly/hapi-fhir-cli.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 31d5fdd86e2..532aec8560a 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -73,13 +73,13 @@ ca.uhn.hapi.fhir hapi-fhir-structures-dstu2 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT compile ca.uhn.hapi.fhir hapi-fhir-jpaserver-subscription - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT compile @@ -96,7 +96,7 @@ ca.uhn.hapi.fhir hapi-fhir-testpage-overlay - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT classes diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java index 29b7a8dba03..c9079b7898a 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java @@ -23,6 +23,7 @@ package ca.uhn.hapi.fhir.docs; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.PerformanceOptionsEnum; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SearchStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; @@ -235,21 +236,27 @@ public class GenericClientExample { // START SNIPPET: conformance // Retrieve the server's conformance statement and print its // description - CapabilityStatement conf = client.capabilities().ofType(CapabilityStatement.class).execute(); + CapabilityStatement conf = client + .capabilities() + .ofType(CapabilityStatement.class) + .execute(); System.out.println(conf.getDescriptionElement().getValue()); // END SNIPPET: conformance } { // START SNIPPET: delete - IBaseOperationOutcome resp = client.delete().resourceById(new IdType("Patient", "1234")).execute(); + MethodOutcome response = client + .delete() + .resourceById(new IdType("Patient", "1234")) + .execute(); // outcome may be null if the server didn't return one - if (resp != null) { - OperationOutcome outcome = (OperationOutcome) resp; - System.out.println(outcome.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); - } - // END SNIPPET: delete - } + OperationOutcome outcome = (OperationOutcome) response.getOperationOutcome(); + if (outcome != null) { + System.out.println(outcome.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + } + // END SNIPPET: delete + } { // START SNIPPET: deleteConditional client.delete() @@ -262,6 +269,19 @@ public class GenericClientExample { .execute(); // END SNIPPET: deleteConditional } + { + // START SNIPPET: deleteCascade + client.delete() + .resourceById(new IdType("Patient/123")) + .cascade(DeleteCascadeModeEnum.DELETE) + .execute(); + + client.delete() + .resourceConditionalByType("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) + .execute(); + // END SNIPPET: deleteCascade + } { // START SNIPPET: search Bundle response = client.search() @@ -342,7 +362,8 @@ public class GenericClientExample { .revInclude(Provenance.INCLUDE_TARGET) .lastUpdated(new DateRangeParam("2011-01-01", null)) .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME).limitTo(123) + .sort().descending(Patient.NAME) + .count(123) .returnBundle(Bundle.class) .execute(); // END SNIPPET: searchAdv diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/PartitionExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/PartitionExamples.java new file mode 100644 index 00000000000..ad565512217 --- /dev/null +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/PartitionExamples.java @@ -0,0 +1,133 @@ +package ca.uhn.hapi.fhir.docs; + +/*- + * #%L + * HAPI FHIR - Docs + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.springframework.beans.factory.annotation.Autowired; + +@SuppressWarnings("InnerClassMayBeStatic") +public class PartitionExamples { + + public void multitenantServer() { + + } + + + // START SNIPPET: partitionInterceptorRequestPartition + @Interceptor + public class RequestTenantPartitionInterceptor { + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) + public RequestPartitionId PartitionIdentifyCreate(ServletRequestDetails theRequestDetails) { + return extractPartitionIdFromRequest(theRequestDetails); + } + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) + public RequestPartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) { + return extractPartitionIdFromRequest(theRequestDetails); + } + + private RequestPartitionId extractPartitionIdFromRequest(ServletRequestDetails theRequestDetails) { + // We will use the tenant ID that came from the request as the partition name + String tenantId = theRequestDetails.getTenantId(); + return RequestPartitionId.fromPartitionName(tenantId); + } + + } + // END SNIPPET: partitionInterceptorRequestPartition + + + // START SNIPPET: partitionInterceptorHeaders + @Interceptor + public class CustomHeaderBasedPartitionInterceptor { + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) + public RequestPartitionId PartitionIdentifyCreate(ServletRequestDetails theRequestDetails) { + String partitionName = theRequestDetails.getHeader("X-Partition-Name"); + return RequestPartitionId.fromPartitionName(partitionName); + } + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) + public RequestPartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) { + String partitionName = theRequestDetails.getHeader("X-Partition-Name"); + return RequestPartitionId.fromPartitionName(partitionName); + } + + } + // END SNIPPET: partitionInterceptorHeaders + + + // START SNIPPET: partitionInterceptorResourceContents + @Interceptor + public class ResourceTypePartitionInterceptor { + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) + public RequestPartitionId PartitionIdentifyCreate(IBaseResource theResource) { + if (theResource instanceof Patient) { + return RequestPartitionId.fromPartitionName("PATIENT"); + } else if (theResource instanceof Observation) { + return RequestPartitionId.fromPartitionName("OBSERVATION"); + } else { + return RequestPartitionId.fromPartitionName("OTHER"); + } + } + + } + // END SNIPPET: partitionInterceptorResourceContents + + + // START SNIPPET: multitenantServer + public class MultitenantServer extends RestfulServer { + + @Autowired + private PartitionSettings myPartitionSettings; + + @Override + protected void initialize() { + + // Enable partitioning + myPartitionSettings.setPartitioningEnabled(true); + + // Set the tenant identification strategy + setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); + + // Use the tenant ID supplied by the tenant identification strategy + // to serve as the partitioning ID + registerInterceptor(new RequestTenantPartitionInterceptor()); + + // ....Register some providers and other things.... + + } + } + // END SNIPPET: multitenantServer + + +} diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java index a0f83c5132f..eaf84d6ce1f 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java @@ -20,22 +20,23 @@ package ca.uhn.hapi.fhir.docs; * #L% */ +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.*; import ca.uhn.fhir.validation.ResultSeverityEnum; -import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import java.util.Arrays; -@SuppressWarnings("serial") +@SuppressWarnings({"serial", "RedundantThrows", "InnerClassMayBeStatic"}) public class ServletExamples { // START SNIPPET: loggingInterceptor - @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") + @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") public class RestfulServerWithLogging extends RestfulServer { @Override @@ -65,15 +66,17 @@ public class ServletExamples { public class ValidatingServerWithLogging extends RestfulServer { @Override - protected void initialize() throws ServletException { - + protected void initialize() { + FhirContext ctx = FhirContext.forDstu3(); + setFhirContext(ctx); + // ... define your resource providers here ... // Create an interceptor to validate incoming requests RequestValidatingInterceptor requestInterceptor = new RequestValidatingInterceptor(); // Register a validator module (you could also use SchemaBaseValidator and/or SchematronBaseValidator) - requestInterceptor.addValidatorModule(new FhirInstanceValidator()); + requestInterceptor.addValidatorModule(new FhirInstanceValidator(ctx)); requestInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); requestInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); @@ -86,7 +89,7 @@ public class ServletExamples { // Create an interceptor to validate responses // This is configured in the same way as above ResponseValidatingInterceptor responseInterceptor = new ResponseValidatingInterceptor(); - responseInterceptor.addValidatorModule(new FhirInstanceValidator()); + responseInterceptor.addValidatorModule(new FhirInstanceValidator(ctx)); responseInterceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); responseInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); responseInterceptor.setResponseHeaderValue("Validation on ${line}: ${message} ${severity}"); @@ -118,6 +121,24 @@ public class ServletExamples { } // END SNIPPET: exceptionInterceptor + // START SNIPPET: fhirPathInterceptor + @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") + public class RestfulServerWithFhirPath extends RestfulServer { + + @Override + protected void initialize() throws ServletException { + + // ... define your resource providers here ... + + // Now register the interceptor + FhirPathFilterInterceptor interceptor = new FhirPathFilterInterceptor(); + registerInterceptor(interceptor); + + } + + } + // END SNIPPET: fhirPathInterceptor + // START SNIPPET: responseHighlighterInterceptor @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server") public class RestfulServerWithResponseHighlighter extends RestfulServer { diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidateDirectory.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidateDirectory.java index eee7ae36e95..e2e4bac7d2f 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidateDirectory.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidateDirectory.java @@ -25,10 +25,10 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.ValueSet; @@ -56,9 +56,9 @@ public class ValidateDirectory { IParser xmlParser = ctx.newXmlParser(); IParser jsonParser = ctx.newJsonParser(); - Map structureDefinitions = new HashMap(); - Map codeSystems = new HashMap(); - Map valueSets = new HashMap(); + Map structureDefinitions = new HashMap<>(); + Map codeSystems = new HashMap<>(); + Map valueSets = new HashMap<>(); // Load all profile files for (File nextFile : profileDirectory.listFiles()) { @@ -90,11 +90,11 @@ public class ValidateDirectory { } } - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx); ValidationSupportChain validationSupportChain = new ValidationSupportChain(); - validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); - validationSupportChain.addValidationSupport(new PrePopulatedValidationSupport(structureDefinitions, valueSets, codeSystems)); + validationSupportChain.addValidationSupport((ca.uhn.fhir.context.support.IValidationSupport) new DefaultProfileValidationSupport(ctx)); + validationSupportChain.addValidationSupport((ca.uhn.fhir.context.support.IValidationSupport) new PrePopulatedValidationSupport(ctx, structureDefinitions, valueSets, codeSystems)); instanceValidator.setValidationSupport(validationSupportChain); diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java index 29b5ad67ad2..082d278dd86 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java @@ -21,22 +21,36 @@ package ca.uhn.hapi.fhir.docs; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.validation.*; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.SchemaBaseValidator; +import ca.uhn.fhir.validation.SingleValidationMessage; +import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.r4.model.ContactPoint; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import javax.servlet.ServletException; import java.io.File; @@ -48,27 +62,31 @@ public class ValidatorExamples { public void validationIntro() { // START SNIPPET: validationIntro - FhirContext ctx = FhirContext.forR4(); - - // Ask the context for a validator - FhirValidator validator = ctx.newValidator(); - - // Create a validator modules and register it - IValidatorModule module = new FhirInstanceValidator(); - validator.registerValidatorModule(module); + FhirContext ctx = FhirContext.forR4(); - // Pass a resource in to be validated. The resource can - // be an IBaseResource instance, or can be a raw String - // containing a serialized resource as text. - Patient resource = new Patient(); - ValidationResult result = validator.validateWithResult(resource); - String resourceText = ""; - ValidationResult result2 = validator.validateWithResult(resourceText); - - // The result object now contains the validation results - for (SingleValidationMessage next : result.getMessages()) { - System.out.println(next.getLocationString() + " " + next.getMessage()); - } + // Ask the context for a validator + FhirValidator validator = ctx.newValidator(); + + // Create a validation module and register it + IValidatorModule module = new FhirInstanceValidator(ctx); + validator.registerValidatorModule(module); + + // Pass a resource instance as input to be validated + Patient resource = new Patient(); + resource.addName().setFamily("Simpson").addGiven("Homer"); + ValidationResult result = validator.validateWithResult(resource); + + // The input can also be a raw string (this mechanism can + // potentially catch syntax issues that would have been missed + // otherwise, since the HAPI FHIR Parser is forgiving about + // its input. + String resourceText = ""; + ValidationResult result2 = validator.validateWithResult(resourceText); + + // The result object now contains the validation results + for (SingleValidationMessage next : result.getMessages()) { + System.out.println(next.getLocationString() + " " + next.getMessage()); + } // END SNIPPET: validationIntro } @@ -177,9 +195,16 @@ public class ValidatorExamples { // START SNIPPET: instanceValidator FhirContext ctx = FhirContext.forR4(); + // Create a validation support chain + ValidationSupportChain validationSupportChain = new ValidationSupportChain( + new DefaultProfileValidationSupport(ctx), + new InMemoryTerminologyServerValidationSupport(ctx), + new CommonCodeSystemsTerminologyService(ctx) + ); + // Create a FhirInstanceValidator and register it to a validator FhirValidator validator = ctx.newValidator(); - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupportChain); validator.registerValidatorModule(instanceValidator); /* @@ -229,75 +254,56 @@ public class ValidatorExamples { // Create a FhirInstanceValidator and register it to a validator FhirValidator validator = ctx.newValidator(); - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx); validator.registerValidatorModule(instanceValidator); - - IValidationSupport valSupport = new IValidationSupport() { + + IValidationSupport valSupport = new IValidationSupport() { @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { + public List fetchAllConformanceResources() { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public List fetchAllConformanceResources(FhirContext theContext) { + public ValueSet fetchValueSet(String theSystem) { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { + public T fetchResource(Class theClass, String theUri) { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { + public StructureDefinition fetchStructureDefinition(String theUrl) { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - // TODO: implement (or return null if your implementation does not support this function) - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - // TODO: implement (or return null if your implementation does not support this function) - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - // TODO: implement (or return null if your implementation does not support this function) - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { // TODO: implement (or return null if your implementation does not support this function) return false; } @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { // TODO: implement (or return null if your implementation does not support this function) return null; } @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - // TODO: implement (or return null if your implementation does not support this function) - return null; + public FhirContext getFhirContext() { + return ctx; } }; @@ -309,7 +315,7 @@ public class ValidatorExamples { * which loads the default HL7 versions. Any StructureDefinitions which are not found in * the built-in set are delegated to your custom implementation. */ - ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(), valSupport); + ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), valSupport); instanceValidator.setValidationSupport(support); // END SNIPPET: instanceValidatorCustom @@ -325,33 +331,72 @@ public class ValidatorExamples { // START SNIPPET: validateSupplyProfiles FhirContext ctx = FhirContext.forR4(); - // Create a PrePopulatedValidationSupport and load it with our custom structures - PrePopulatedValidationSupport prePopulatedSupport = new PrePopulatedValidationSupport(); + // Create a chain that will hold our modules + ValidationSupportChain supportChain = new ValidationSupportChain(); + // DefaultProfileValidationSupport supplies base FHIR definitions. This is generally required + // even if you are using custom profiles, since those profiles will derive from the base + // definitions. + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ctx); + supportChain.addValidationSupport(defaultSupport); + + // Create a PrePopulatedValidationSupport which can be used to load custom definitions. // In this example we're loading two things, but in a real scenario we might // load many StructureDefinitions, ValueSets, CodeSystems, etc. + PrePopulatedValidationSupport prePopulatedSupport = new PrePopulatedValidationSupport(ctx); prePopulatedSupport.addStructureDefinition(someStructureDefnition); prePopulatedSupport.addValueSet(someValueSet); - - // We'll still use DefaultProfileValidationSupport since derived profiles generally - // rely on built-in profiles also being available - DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); - - // We'll create a chain that includes both the pre-populated and default. We put - // the pre-populated (custom) support module first so that it takes precedence - ValidationSupportChain supportChain = new ValidationSupportChain(); supportChain.addValidationSupport(prePopulatedSupport); - supportChain.addValidationSupport(defaultSupport); + + // Wrap the chain in a cache to improve performance + CachingValidationSupport cache = new CachingValidationSupport(supportChain); // Create a validator using the FhirInstanceValidator module. We can use this // validator to perform validation - FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain); + FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache); FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule); ValidationResult result = validator.validateWithResult(input); // END SNIPPET: validateSupplyProfiles } - + + + public void validateUsingRemoteTermServer() { + + StructureDefinition someStructureDefnition = null; + ValueSet someValueSet = null; + String input = null; + + // START SNIPPET: validateUsingRemoteTermSvr + FhirContext ctx = FhirContext.forR4(); + + // Create a chain that will hold our modules + ValidationSupportChain supportChain = new ValidationSupportChain(); + + // DefaultProfileValidationSupport supplies base FHIR definitions. This is generally required + // even if you are using custom profiles, since those profiles will derive from the base + // definitions. + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ctx); + supportChain.addValidationSupport(defaultSupport); + + // Create a module that uses a remote terminology service + RemoteTerminologyServiceValidationSupport remoteTermSvc = new RemoteTerminologyServiceValidationSupport(ctx); + remoteTermSvc.setBaseUrl("http://hapi.fhir.org/baseR4"); + supportChain.addValidationSupport(remoteTermSvc); + + // Wrap the chain in a cache to improve performance + CachingValidationSupport cache = new CachingValidationSupport(supportChain); + + // Create a validator using the FhirInstanceValidator module. We can use this + // validator to perform validation + FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache); + FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule); + ValidationResult result = validator.validateWithResult(input); + // END SNIPPET: validateUsingRemoteTermSvr + + } + + @SuppressWarnings("unused") private static void validateFiles() throws Exception { // START SNIPPET: validateFiles diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamplesDstu3.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamplesDstu3.java index fb19d5957f7..835bcd28cba 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamplesDstu3.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamplesDstu3.java @@ -22,9 +22,9 @@ package ca.uhn.hapi.fhir.docs; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.validation.FhirValidator; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; public class ValidatorExamplesDstu3 { @@ -43,7 +43,7 @@ public class ValidatorExamplesDstu3 { // FhirInstanceValidator is the validation module that handles // profile validation. So, create an InstanceValidator module // and register it to the validator. - FhirInstanceValidator instanceVal = new FhirInstanceValidator(); + FhirInstanceValidator instanceVal = new FhirInstanceValidator(ctx); validator.registerValidatorModule(instanceVal); // FhirInstanceValidator requires an instance of "IValidationSupport" in @@ -59,7 +59,7 @@ public class ValidatorExamplesDstu3 { // the DefaultProfileValidationSupport, which supplies the "built-in" FHIR // StructureDefinitions and ValueSets ValidationSupportChain validationSupportChain = new ValidationSupportChain(); - validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); + validationSupportChain.addValidationSupport((ca.uhn.fhir.context.support.IValidationSupport) new DefaultProfileValidationSupport(ctx)); instanceVal.setValidationSupport(validationSupportChain); // END SNIPPET: validateFiles diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/interceptor/MyTestInterceptor.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/interceptor/MyTestInterceptor.java index 829b6f63245..868a796d786 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/interceptor/MyTestInterceptor.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/interceptor/MyTestInterceptor.java @@ -23,8 +23,8 @@ package ca.uhn.hapi.fhir.docs.interceptor; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; /** * Interceptor class diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json index c33856d2b24..248f9ec787c 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/atlas/points.json @@ -20,6 +20,7 @@ "contactName": "Kevin Mayfield", "contactEmail": "Kevin.mayfield@mayfield-is.co.uk", "link": "https://data.developer.nhs.uk/ccri/exp", + "city": "UK", "lat": 55.378052, "lon": -3.435973, "added": "2019-08-19" @@ -29,6 +30,7 @@ "contactName": "David Hay", "contactEmail": "david.hay25@gmail.com", "link": "http://clinfhir.com", + "city": "New Zealand", "lat": -42.651737, "lon": 171.926909, "added": "2019-08-18" @@ -324,6 +326,17 @@ "city": "Germany", "lat": 51.165691, "lon": 10.451526 + }, + { + "title": "VEIG", + "description": "Vietnam Ehealth Innovation Group", + "link": "http://emr.com.vn", + "contactName": "Nguyen Hai Phong", + "contactEmail": "haiphong.nguyen@gmail.com", + "city": "Hanoi,Vietnam", + "lat": 21.026058, + "lon": 105.822715, + "added": "2020-03-06" } ] diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_2_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_2_0/changes.yaml index 2ac8d2c2173..f2227e14ecd 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_2_0/changes.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_2_0/changes.yaml @@ -75,7 +75,7 @@ issue: "1658" type: "fix" title: "When parsing HTML Narratives, the `lang` attribute was stripped from the outer DIV tag if present. Thanks to -Sean McIlvenna for reporting!" + Sean McIlvenna for reporting!" - item: issue: "1655" type: "fix" @@ -90,9 +90,8 @@ Sean McIlvenna for reporting!" issue: 1689 title: "A correction was made to the narrative generation documentation. Thanks to GitHub user dionmcm for the pull request!" - title: "A meomery leak was resolved in the JPA terminology service delta upload operations." - item: type: "add" title: "Searching Location.position by latitude, longitude and distance for DSTU3, R4 and R5 is now supported using a simple 'box' search. - Locations falling within a box with length and width of 2 * distance, centred on specified latitude,longitude are matched. If no distance - is provided, the coordinates must match exactly." + Locations falling within a box with length and width of 2 * distance, centred on specified latitude,longitude are matched. If no distance + is provided, the coordinates must match exactly." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1698-remove-search-last-used-column.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1698-remove-search-last-used-column.yaml new file mode 100644 index 00000000000..85376cc33b9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1698-remove-search-last-used-column.yaml @@ -0,0 +1,10 @@ +--- +type: change +issue: 1698 +title: "Removed the SEARCH_LAST_RETURNED column of the HFJ_SEARCH table. HAPI FHIR updated the HFJ_SEARCH table with every +request, but this led to unecessary database load. The purpose of this column was to ensure that search results were +kept around long enough for systems that needed them for paging (default one hour). When expiring search results, +we used to add one hour to SEARCH_LAST_RETURNED to determine the expiry time. However, the length of time where a search +result could be updated was relatively small (default one minute). So rather than keeping track of the expiry time to expire +exactly one hour after the last returned time, hapi now simply expires after the maximum possible length of time (default one hour +plus one minute). This eliminates the need to update the HFJ_SEARCH table with every search." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1728-index-canonical.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1728-index-canonical.yaml new file mode 100644 index 00000000000..5970f650a52 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1728-index-canonical.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 1728 +title: "Fields of type `canonical` were not previously indexed by the JPA server, meaning that some + default search parameters could not be honoured (e.g. StructureDefinition:valueset). This is now corrected." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1732-remove-deleted-from-results.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1732-remove-deleted-from-results.yaml new file mode 100644 index 00000000000..070819f9846 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1732-remove-deleted-from-results.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1732 +title: In the JPA server, quickly deleting a resource and then performing a query that had recently returned that + search result could cause a cached stub resource (containing no data but with an ID and metadata populated) to + be returned. This has been corrected. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1736-defer-large-terminology-delta-adds.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1736-defer-large-terminology-delta-adds.yaml new file mode 100644 index 00000000000..ba7d3a1f7df --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1736-defer-large-terminology-delta-adds.yaml @@ -0,0 +1,7 @@ +--- +type: add +issue: 1736 +title: When performing large terminology concept additions via the delta addition service, concepts will + now be added via the deferred storage service, meaning that they will be added in small incremental batches + instead of as a part of one large transaction. This helps to avoid timeouts and memory issues when uploading + large collections of concepts. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-error-on-missing-profile-validation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-error-on-missing-profile-validation.yaml new file mode 100644 index 00000000000..c468181274e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-error-on-missing-profile-validation.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1742 +title: "When validating a resource, the validator will now report an error if the resource declares conformance + to an unknown or invalid profile URL via the `Resource.meta.profile` declaration. Previously this was a warning + and did not block successful validation." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-limit-type-on-has.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-limit-type-on-has.yaml new file mode 100644 index 00000000000..4397032c2aa --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-limit-type-on-has.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1742 +title: "When performing a search in the JPA server where the only parameter was a `_has` parameter, + the server did not respect the resource typename being searched for, causing false positive + search results. This has been corrected." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-terminology-delta-defer.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-terminology-delta-defer.yaml new file mode 100644 index 00000000000..bdc0dff48f5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1742-terminology-delta-defer.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1742 +title: When performing a terminology delta ADD operation, if the number of codes being added is large + the codes will be added in small batches via an asynchronous scheduled task in order to avoid overwhelming + the database with a large operation. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1759-efficiency-in-search-deleting.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1759-efficiency-in-search-deleting.yaml new file mode 100644 index 00000000000..745fd4e241d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1759-efficiency-in-search-deleting.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1759 +title: When deleting searches from the query cache where a large number of searches with a large + number of results were present, the system would repeatedly mark the same rows as deletion + candidates. This put unneccessary pressure on the database and has been corrected. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1760-support-chained-parameters-in-has.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1760-support-chained-parameters-in-has.yaml new file mode 100644 index 00000000000..bf22058eab4 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1760-support-chained-parameters-in-has.yaml @@ -0,0 +1,9 @@ +--- +type: add +issue: 1760 +title: "Adds support for chained parameters in a _has query. For example + `GET /Patient?_has:Observation:subject:device.identifier=1234-5`. Adds a performance warning on any queries that use + an unqualified resource in a chain which ends up resolving to 2 or more candidate target types. Thanks to Jean-Francois Briere + for the patch." + + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1761-avoid-duplicate_restype.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1761-avoid-duplicate_restype.yaml new file mode 100644 index 00000000000..20530d9b926 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1761-avoid-duplicate_restype.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 1761 +title: "A minor regression in 4.2.0 was introduced, where JPA searches using the `_id` search parameter often had + a duplicate SQL predicate in their where clause. This issue may not have caused any bad effects on some environments, + but it did look strange in SQL logs and has been corrected." + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml new file mode 100644 index 00000000000..cc246038dcf --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1763 +title: In servers, when requesting _summary=count, the response Bundle.type value was filtered, leading + to an invalid response bundle. This has been corrected. Thanks to GitHub user @Legi429 for reporting! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml new file mode 100644 index 00000000000..c6e9a32c0d5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1769 +title: A new built-in server interceptor called FhirPathFilterInterceptor has been added. This interceptor + evaluates an arbitrary FHIRPath expression against the resource being returned and replaces the response + with a Parameters resource containing the results of the evaluation. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml new file mode 100644 index 00000000000..6ad1da006db --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1770 +title: When parsing Bundles, contained resoures from other entries in the Bundle could incorrecly be + stitched into a target resource if they had the same local ID. Thanks to August Langhout for + the pull request! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml new file mode 100644 index 00000000000..61454172e40 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 1772 +title: "The JPA server now allows chained searches on the `_type` parameter. For example, the following + could be used to find all Encounters with a context of type Group: `Encounter?subject._type=Group`." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1774-reject-invalid-params-on-read.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1774-reject-invalid-params-on-read.yaml new file mode 100644 index 00000000000..04f52b694f7 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1774-reject-invalid-params-on-read.yaml @@ -0,0 +1,7 @@ +--- +type: add +issue: 1774 +title: The REST Server will now raise an error if a client tries to perform a FHIR *read* operation while using + URL paramaters that are specific to FHIR *search* operations. This should help clients who are mistaking the + semantics between the two operations, as previously the search parameters were simply ignored leading to + confusion. Thanks to Jafer Khan for implementing this! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml new file mode 100644 index 00000000000..c67ab01baa0 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1776 +title: A new server interceptor called `ResponseSizeCapturingInterceptor` has been added. This interceptor captures and makes + available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response + stream for FHIR responses. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml new file mode 100644 index 00000000000..d7c698890e9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1778 +title: "When encoding a resource, a crash could occur if the resource had a contained Bundle resource. This + is not commonly done, but there are valid scenarios for doing so." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-to-client-pointcuts.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-to-client-pointcuts.yaml new file mode 100644 index 00000000000..b58ccd1027d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-to-client-pointcuts.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1783 +title: "The client interceptor pointcuts `CLIENT_REQUEST` and `CLIENT_RESPONSE` now allow a parameter of type `IRestfulClient` to be + injected, containing a reference to the client making the request. In addition, CLIENT_REQUEST interceptors are now able to + modify the URL of the request before it is performed." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-urltenantselectioninterceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-urltenantselectioninterceptor.yaml new file mode 100644 index 00000000000..ac5d904c188 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-add-client-urltenantselectioninterceptor.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1783 +title: "A new client interceptor called UrlTenantSelectionInterceptor has been added. This interceptor allows the dynamic + selection of a tenant ID on servers that are using URL Base Tenant Selection." + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-error-on-overriding-default-searchparams.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-error-on-overriding-default-searchparams.yaml new file mode 100644 index 00000000000..b9dd6eb0c04 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-error-on-overriding-default-searchparams.yaml @@ -0,0 +1,8 @@ +--- +type: add +issue: 1783 +title: "In the JPA server, the ModelConfig setting 'DefaultSearchParamsCanBeOverridden' now has a default value of + *true* (previously this was *false*). In addition, when creating/updating a SearchParameter resource, the system + will now raise an error if the client is attempting to override a built-in SearchParameter when this setting + is disabled (previously this was silently ignored)." + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml new file mode 100644 index 00000000000..3295f8fa849 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1783-fail-if-overriding-sps-is-disabled.yaml @@ -0,0 +1,7 @@ +--- +type: add +issue: 1783 +title: "In the JPA sevrer, if overriding built-in search parameters is not enabled, the server + will now return an error if a client tries to create a SearchParameter that is + trying to override one. Previously, the SearchParameter would be stored but silently ignored, + which was confusing." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml new file mode 100644 index 00000000000..50ea2dd8c2a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1788-improve-apacheproxyaddressstrategy.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 1788 +title: "The ApacheProxyAddressStrategy has been improved to add support for additional proxy headers inclusing + `X-Forwarded-Host`, `X-Forwarded-Proto`, `X-Forwarded-Port`, and `X-Forwarded-Prefix`. Thanks to Thomas Papke + for the pull request!" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1791-fix-graphql-argument-error.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1791-fix-graphql-argument-error.yaml new file mode 100644 index 00000000000..26b9785b454 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1791-fix-graphql-argument-error.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1791 +title: The GraphQL Expression parser sometimes fails and reports unhelpful error messages when using search arguments. + Thanks to Ibrohim Kholilul Islam for the pull request! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1793-report-attr-name-in-json-parse-errors.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1793-report-attr-name-in-json-parse-errors.yaml new file mode 100644 index 00000000000..ad2b30fa92a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1793-report-attr-name-in-json-parse-errors.yaml @@ -0,0 +1,7 @@ +--- +type: add +issue: 1793 +title: "When parsing JSON resources, if an element contains an invalid value, the Parser Error Handler + did not have access to the actual name of the element being parsed. This meant that errors lacked useful + detail in order to diagnose the issue. This has been corrected. Thanks to GitHub user + @jwalter for reporting!" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1794-allow-client-id-none-server-id-uuid.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1794-allow-client-id-none-server-id-uuid.yaml new file mode 100644 index 00000000000..f1a460b7698 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1794-allow-client-id-none-server-id-uuid.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1794 +title: A bug in the JPA server prevented Client Resource ID mode from being set to NOT_ALLOWED when Server Resource ID mode + was set to UUID. Thanks to GitHub user @G-2-Z for reporting! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1797-add-currency-validation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1797-add-currency-validation.yaml new file mode 100644 index 00000000000..f5811939f9f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1797-add-currency-validation.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 1797 +title: The HAPI FHIR instance validator now includes validation for currency types (ISO 4217) diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1798-add-millis-methods-to-client.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1798-add-millis-methods-to-client.yaml new file mode 100644 index 00000000000..98f7870a095 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1798-add-millis-methods-to-client.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 1798 +title: New mthods have been added to DateClientParam allowing searching at MILLIS precision. Thanks to + David Gileadi for the pull request! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-allow-parsing-contained-in-jpa.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-allow-parsing-contained-in-jpa.yaml new file mode 100644 index 00000000000..d584a568306 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-allow-parsing-contained-in-jpa.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 1801 +title: "When invoking JPA DAO methods programatically to store a resource (as opposed to using the FHIR REST API), if + the resource being stored had any contained resources, these would sometimes not be visible to the search parameter + indexer, leading to missing search params. This is a very fringe use case, but a workaround has been put in place to + solve it." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-handle-persisted-leading-decimals.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-handle-persisted-leading-decimals.yaml new file mode 100644 index 00000000000..7db84649235 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1801-handle-persisted-leading-decimals.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 1801 +title: "In previous versions of HAPI FHIR, the server incorrectly silently accepted decimal numbers in JSON with no + leading numbers (e.g. `.123`), as well as decimal numbers with more than one leading zero (e.g. 00.123). These will + now be rejected by the JSON parser. Any values with this string that have previously been stored in the JPA server + database will now automatically normalize the value to `0.123`." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1802-improve-search-binding-method-priority.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1802-improve-search-binding-method-priority.yaml new file mode 100644 index 00000000000..180ade87229 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1802-improve-search-binding-method-priority.yaml @@ -0,0 +1,8 @@ +--- +type: add +issue: 1802 +title: "In a plain server, if a Resource Provider class had two methods with the same parameter names + (as specified in the @OptionalParam or @RequiredParam) but different cardinalities, the server could + sometimes pick the incorrect method to execute. The selection algorithm has been improved to no longer + have this issue, and to be more consistent and predictable in terms of which resource provider + method is selected when the choice is somewhat ambiguous." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1804-add-cascading-delete-to-client.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1804-add-cascading-delete-to-client.yaml new file mode 100644 index 00000000000..9e07670bf5f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1804-add-cascading-delete-to-client.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 1804 +title: Support for HAPI FHIR cascading deletes has been added to the Generic Client. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1806-fix-aws-managed-elasticsearch.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1806-fix-aws-managed-elasticsearch.yaml new file mode 100644 index 00000000000..575c95bcd6e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1806-fix-aws-managed-elasticsearch.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 1806 +title: The JPA server ElasticSearch provider failed to initialize if username/password credentials were not + explicitly provided, meaning it could not run on AWS-supplied ElasticSearch. Thanks to Maciej Kucharek for + the pull request! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1810-improve-tespage-warning-text.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1810-improve-tespage-warning-text.yaml new file mode 100644 index 00000000000..9f14ec5f3ea --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1810-improve-tespage-warning-text.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 1810 +title: The text styling on the Testpage Overlay homepage has been improved to use native + Bootstrap warning colours. Thanks to Joel Schneider for the pull request! diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml new file mode 100644 index 00000000000..bc38dd31845 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml @@ -0,0 +1,63 @@ +--- +- item: + type: "add" + title: "The version of a few dependencies have been bumped to the latest versions + (dependent HAPI modules listed in brackets): +
          +
        • Hibernate ORM (JPA): 5.4.6 -> 5.4.14
        • +
        • Hibernate Search (JPA): 5.11.3 -> 5.11.5
        • +
        • Hibernate Validator (JPA): 5.4.2.Final -> 6.1.3.Final
        • +
        • Guava (JPA): 28.0 -> 28.2
        • +
        • Spring Boot (Boot): 2.2.0.RELEASE -> 2.2.6.RELEASE
        • +
        " +- item: + issue: "1583" + type: "fix" + title: "**Breaking Change**: + The HAPI FHIR Validation infrastructure has changed significantly under the hood. Existing users of the + validator may need to change package declarations (as FhirInstanceValidator and several other related classes + have been moved) and potentially add new modules to their Validation Support Chain. See + [Migrating to HAPI FHIR 5.x](/hapi-fhir/docs/validation/instance_validator.html#migrating-to-hapi-fhir-5x) + for details on how to account for this change in your code. + " +- item: + issue: "1769" + type: "change" + title: "**Breaking Change**: + The `IFluentPath` interface has been renamed to `IFhirPath`, and the `FhirContext#newFluentPath()` method + has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially + called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming. + " +- item: + issue: "1790" + type: "change" + title: "**Breaking Change**: + Several classes in the JPA server have been moved to new packages, including the DaoConfig and IDao interfaces. + These classes have not changed in terms of functionality, but existing projects may need to adjust some + package import statements. + " +- item: + issue: "1804" + type: "change" + title: "**Breaking Change**: + The Generic/Fluent **delete()** operation now returns a [MethodOutcome](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/api/MethodOutcome.html) + object instead of an OperationOutcome. The OperationOutcomoe is still available direcly by querying + the MethodOutcome object, but this change makes the delete() method more consistent with + other similar methods in the API. + " +- item: + type: "change" + title: "**Breaking Change**: + Some R4 and R5 structure fields containing a `code` value with a **Required (closed) binding** + did not use the java Enum type that was generated for the given field. These have been changed + to use the Enum values where possible. This change does not remove any functionality from the model + but may require a small amount of re-coding to deal with new setter/getter types on a few fields. + " +- item: + issue: "1807" + type: "change" + title: "**New Feature**: + A new feature has been added to the JPA server called **[Partitioning](/hapi-fhir/docs/server_jpa/partitioning.html). This + feature allows data to be segregated using a user defined partitioning strategy. This can be leveraged to take + advantags of native RDBMS partition strategies, and also to implement **multitenant servers**. + " diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/near-chain.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/near-chain.yaml new file mode 100644 index 00000000000..64c83acf7ad --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/near-chain.yaml @@ -0,0 +1,4 @@ +--- +type: fix +title: "DSTU3 searches using near-distance only worked on Location resources directly. It now works on chained searches on resources with a location. +E.g. PractitionerRole?location.near-distance=1.0 now works properly." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/javadocs.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/javadocs.md index e41387d8fbf..6a81c70349c 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/javadocs.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/javadocs.md @@ -8,7 +8,9 @@ See the [Modules Page](/docs/introduction/modules.html) for more information on * [Model API (R4)](/apidocs/hapi-fhir-structures-r4/) - hapi-fhir-structures-r4 * [Model API (R5)](/apidocs/hapi-fhir-structures-r5/) - hapi-fhir-structures-r5 * [Client API](/apidocs/hapi-fhir-client/) - hapi-fhir-client -* [Server API (Plain)](/apidocs/hapi-fhir-server/) - hapi-fhir-server -* [Server API (JPA)](/apidocs/hapi-fhir-jpaserver-base/) - hapi-fhir-jpaserver-base +* [Plain Server API](/apidocs/hapi-fhir-server/) - hapi-fhir-server +* [JPA Server - API](/apidocs/hapi-fhir-jpaserver-api/) - hapi-fhir-jpaserver-api +* [JPA Server - Model](/apidocs/hapi-fhir-jpaserver-model/) - hapi-fhir-jpaserver-model +* [JPA Server - Base](/apidocs/hapi-fhir-jpaserver-base/) - hapi-fhir-jpaserver-base * [Version Converter API](/apidocs/hapi-fhir-converter/) - hapi-fhir-converter * [Server API (JAX-RS)](/apidocs/hapi-fhir-jaxrsserver-base/) - hapi-fhir-jaxrsserver-base diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md index 1af92981bd4..a9bf2d8b5cf 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md @@ -62,5 +62,5 @@ To enable detailed logging of client requests and responses (what URL is being r # Server Request Logging -To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptr](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information. +To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptor](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md index d28aea66dd6..45838177186 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md @@ -5,7 +5,7 @@ HAPI also provides a second style of client, called the *annotation-driven* clie The design of the annotation-driven client is intended to be similar to that of JAX-WS, so users of that specification should be comfortable with this one. It uses a user-defined interface containing special annotated methods which HAPI binds to calls against a server. -The annotation-driven client is particularly useful if you have a server that exposes a set of specific operations (search parameter combinations, named queries, etc.) and you want to let developers have a stongly/statically typed interface to that server. +The annotation-driven client is particularly useful if you have a server that exposes a set of specific operations (search parameter combinations, named queries, etc.) and you want to let developers have a strongly/statically typed interface to that server. There is no difference in terms of capability between the two styles of client. There is simply a difference in programming style and complexity. It is probably safe to say that the generic client is easier to use and leads to more readable code, at the expense of not giving any visibility into the specific capabilities of the server you are interacting with. @@ -40,7 +40,7 @@ Once your client interface is created, all that is left is to create a FhirConte Restful client interfaces that you create will also extend the interface [IRestfulClient](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClient.html), which comes with some helpful methods for configuring the way that the client will interact with the server. -The following snippet shows how to configure the cliet to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently). +The following snippet shows how to configure the client to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently). ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|clientConfig}} diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md index f674a1d8cd7..f73edba269a 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md @@ -10,7 +10,7 @@ This page outlines ways that the client can be configured for specific behaviour By default, the client will query the server before the very first operation to download the server's conformance/metadata statement and verify that the server is appropriate for the given client. This check is only done once per server endpoint for a given FhirContext. -This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unneccesary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check. +This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unnecessary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check. ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|dontValidate}} @@ -36,7 +36,7 @@ REST clients (both Generic and Annotation-Driven) use [Apache HTTP Client](http: The Apache HTTP Client is very powerful and extremely flexible, but can be confusing at first to configure, because of the low-level approach that the library uses. -In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timesouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library. +In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timeouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library. This configuration is provided by accessing the [IRestfulClientFactory](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClientFactory.html) class from the FhirContext. @@ -62,7 +62,7 @@ The following example shows how to configure the use of an HTTP proxy in the cli As of HAPI FHIR 2.0, an alternate client implementation is available. This client replaces the low-level Apache HttpClient implementation with the Square [OkHttp](http://square.github.io/okhttp/) library. -Changing HTTP implementations should be mostly ransparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library. +Changing HTTP implementations should be mostly transparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library. Note that as of HAPI FHIR 2.1, OkHttp is the default provider on Android, and will be used without any configuration being required. This is done because HttpClient is deprecated on Android and has caused problems in the past. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md index 27addc6d280..f79d97cbfff 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md @@ -166,7 +166,7 @@ The server responds with the following response. Note that the ID of the already # Fetch all Pages of a Bundle -This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the resuts. +This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the results. ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleFetcher.java|loadAll}} diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md index 055f2aaf636..5cb269efb1d 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md @@ -22,7 +22,7 @@ Note that most fluent operations end with an `execute()` statement which actuall # Search -Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to proide a complete implementation of the FHIR API search specification via the generic client API. +Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to provide a complete implementation of the FHIR API search specification via the generic client API. ## Search - By Type @@ -66,7 +66,7 @@ If the server supports paging results, the client has a page method which can be ## Search - Composite Parameters -If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being seached. The following example shows the syntax. +If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being searched. The following example shows the syntax. ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|searchComposite}} @@ -181,6 +181,14 @@ Conditional deletions are also possible, which is a form where instead of deleti {{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|deleteConditional}} ``` +## Cascading Delete + +The following snippet shows now to request a cascading delete. Note that this is a HAPI FHIR specific feature and is not supported on all servers. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|deleteCascade}} +``` + # Update - Instance Updating a resource is similar to creating one, except that an ID must be supplied since you are updating a previously existing resource instance. @@ -193,7 +201,7 @@ The following example shows how to perform an update operation using the generic ## Conditional Updates -FHIR also specifies a type of update called "conditional updates", where insetad of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work. +FHIR also specifies a type of update called "conditional updates", where instead of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work. ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|updateConditional}} @@ -233,7 +241,7 @@ To retrieve the server's capability statement, simply call the [`capabilities()` # Extended Operations -FHIR also supports a set of *extended operatioons*, which are operatons beyond the basic CRUD operations defined in the specificiation. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back. +FHIR also supports a set of *extended operations*, which are operations beyond the basic CRUD operations defined in the specification. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back. To invoke an operation using the client, you simply need to create the input [Parameters](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/model/Parameters.html) resource, then pass that to the [`operation()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#operation()) fluent method. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md index 801c261e428..51a1a352e94 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md @@ -1,5 +1,5 @@ # Getting Started with the Client -A starter project containing all nedded dependencies and some starter code for the HAPI FHIR client is available here: +A starter project containing all needed dependencies and some starter code for the HAPI FHIR client is available here: * [hapi-fhirstarters-client-skeleton](https://github.com/FirelyTeam/fhirstarters/tree/master/java/hapi-fhirstarters-client-skeleton/) diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties index 76af2d26c2f..b766e3fa56f 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties @@ -34,15 +34,18 @@ page.server_plain.rest_operations_search=REST Operations: Search page.server_plain.rest_operations_operations=REST Operations: Extended Operations page.server_plain.paging=Paging Search Results page.server_plain.web_testpage_overlay=Web Testpage Overlay -page.server_plain.multitenency=Multitenency +page.server_plain.multitenancy=Multitenancy page.server_plain.jax_rs=JAX-RS Support section.server_jpa.title=JPA Server page.server_jpa.introduction=Introduction page.server_jpa.get_started=Get Started ⚡ page.server_jpa.architecture=Architecture +page.server_jpa.schema=Database Schema page.server_jpa.configuration=Configuration page.server_jpa.search=Search +page.server_jpa.performance=Performance +page.server_jpa.partitioning=Partitioning and Multitenancy page.server_jpa.upgrading=Upgrade Guide section.interceptors.title=Interceptors @@ -64,7 +67,8 @@ page.security.cors=CORS section.validation.title=Validation page.validation.introduction=Introduction page.validation.parser_error_handler=Parser Error Handler -page.validation.profile_validator=Profile Validator +page.validation.instance_validator=Instance Validator +page.validation.validation_support_modules=Validation Support Modules page.validation.schema_validator=Schema/Schematron Validator page.validation.examples=Validation Examples diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resource_links.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resource_links.svg new file mode 100644 index 00000000000..d845b607901 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resource_links.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resources.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resources.svg new file mode 100644 index 00000000000..dd7973051cc --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_resources.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_search_indexes.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_search_indexes.svg new file mode 100644 index 00000000000..c2fcc548ac7 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/jpa_erd_search_indexes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/validation-support-chain.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/validation-support-chain.svg new file mode 100644 index 00000000000..9c0ebe986bd --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/validation-support-chain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md index 243306086fa..0d83f189397 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_client_interceptors.md @@ -75,6 +75,14 @@ The following example shows how to configure your client to inject a bearer toke {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|cookie}} ``` +# Multitenancy: Add tenant ID to path + +When communicating with a server that supports [URL Base Multitenancy](/docs/server_plain/multitenancy.html#url-base-multitenancy), an extra element needs to be added to the request path. This can be done by simply appending the path to the base URL supplied to the client, but it can also be dynamically appended using this interceptor. + +* [UrlTenantSelectionInterceptor JavaDoc](/apidocs/hapi-fhir-client/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.html) +* [UrlTenantSelectionInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptor.java) + + # Performance: GZip Outgoing Request Bodies The GZipContentInterceptor compresses outgoing contents. With this interceptor, if the client is transmitting resources to the server (e.g. for a create, update, transaction, etc.) the content will be GZipped before transmission to the server. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md index 525a8ccdaa3..eaf3690558c 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md @@ -24,14 +24,24 @@ This interceptor will then produce output similar to the following: 2014-09-04 03:30:00.443 Source[127.0.0.1] Operation[search-type Organization] UA[Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)] Params[] ``` + + +# Partitioning: Multitenant Request Partition + +If the JPA server has [partitioning](/docs/server_jpa/partitioning.html) enabled, the RequestTenantPartitionInterceptor can be used in combination with a [Tenant Identification Strategy](/docs/server_plain/multitenancy.html) in order to achieve a multitenant solution. See [JPA Server Partitioning](/docs/server_jpa/partitioning.html) for more information on partitioning. + +* [RequestTenantPartitionInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.html) +* [RequestTenantPartitionInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.java) + + # Response Customizing: Syntax Highlighting -The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get nice formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works. +The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get a nicely formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works. * [ResponseHighlighterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html) * [ResponseHighlighterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java) -To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see no that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the respnose will simply by raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript). +To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see in that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the response will simply be raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript). ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|responseHighlighterInterceptor}} @@ -50,6 +60,52 @@ The following example shows how to register the ExceptionHandlingInterceptor. {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}} ``` +# Response Customizing: Evaluate FHIRPath + +The FhirPathFilterInterceptor looks for a request URL parameter in the form `_fhirpath=(expression)` in all REST requests. If this parameter is found, the value is treated as a [FHIRPath](http://hl7.org/fhirpath/) expression. The response resource will be replaced with a [Parameters](http://hl7.org/fhir/parameters.html) resource containing the results of the given expression applied against the response resource. + +* [FhirPathFilterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.html) +* [FhirPathFilterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java) + +The following example shows how to register the ExceptionHandlingInterceptor. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}} +``` + +An example URL to invoke this function is shown below: + +```url +https://hapi.fhir.org/baseR4/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true +``` + +A sample response to this query is shown below: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "result", + "part": [ { + "name": "expression", + "valueString": "Bundle.entry.resource.as(Patient).name" + }, { + "name": "result", + "valueHumanName": { + "family": "Simpson", + "given": [ "Homer", "Jay" ] + } + }, { + "name": "result", + "valueHumanName": { + "family": "Simpson", + "given": [ "Grandpa" ] + } + } ] + } ] +} +``` + # Validation: Request and Response Validation HAPI FHIR provides a pair of interceptors that can be used to validate incoming requests received by the server, as well as outgoing responses generated by the server. @@ -65,7 +121,7 @@ The RequestValidatingInterceptor looks at resources coming into the server (e.g. These interceptors can be configured to add headers to the response, fail the response (returning an HTTP 422 and throwing an exception in the process), or to add to the OperationOutcome returned by the server. -See [Profile Validator](/docs/validation/profile_validator.html) for information on how validation works in HAPI FHIR. +See [Instance Validator](/docs/validation/instance_validator.html) for information on how validation works in HAPI FHIR. The following example shows how to register this interceptor within a HAPI FHIR REST server. @@ -100,6 +156,13 @@ Some security audit tools require that servers return an HTTP 405 if an unsuppor * [BanUnsupportedHttpMethodsInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.html) * [BanUnsupportedHttpMethodsInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.java) +# Subscription: Subscription Debug Log Interceptor + +When using Subscriptions, the debug log interceptor can be used to add a number of additional lines to the server logs showing the internals of the subscription processing pipeline. + +* [SubscriptionDebugLogInterceptor JavaDoc](/apidocs/hapi-fhir-jpaserver-subscription/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.html) +* [SubscriptionDebugLogInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.java) + # Request Pre-Processing: Override Meta.source @@ -108,3 +171,10 @@ If you wish to override the value of `Resource.meta.source` using the value supp * [CaptureResourceSourceFromHeaderInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html) * [CaptureResourceSourceFromHeaderInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.java) +# Utility: ResponseSizeCapturingInterceptor + +The ResponseSizeCapturingInterceptor can be used to capture the number of characters written in each HTTP FHIR response. + +* [ResponseSizeCapturingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.html) +* [ResponseSizeCapturingInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java) + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md index 56d11da723c..20b86b004ba 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md @@ -2,7 +2,7 @@ HAPI FHIR 3.8.0 introduced a new interceptor framework that is used across the entire library. In previous versions of HAPI FHIR, a "Server Interceptor" framework existed and a separate "Client Interceptor" framework existed. These have now been combined into a single unified (and much more powerful) framework. -Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed do be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor. +Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed to be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor. The following example shows a very simple interceptor example. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md index 58165c42ce9..93c436b42ca 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md @@ -59,7 +59,7 @@ compile 'ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${project.version}' The HAPI FHIR project generally releases a new full software release 4 times per year, or approximately every 3 months. -Generally speaking it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version. +Generally speaking, it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version. ## Using Snapshot Builds @@ -69,8 +69,6 @@ Using a snapshot build generally involves appending *-SNAPSHOT* to the version n ### Using Maven: -To use a snapshot build, you - ```xml @@ -109,7 +107,7 @@ XML processing (for resource marshalling and unmarshalling) uses the Java StAX A Upon starting up, HAPI will emit a log line indicating which StAX implementation is being used, e.g: ``` -08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-phrocessor' version '4.4.0' +08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-processor' version '4.4.0' ``` Although most testing is done using the Woodstox implementation of StAX, it is not required and HAPI should work correctly with any compliant implementation of StAX. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md index 27e2f13f6a8..1f063820f7b 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md @@ -26,7 +26,7 @@ Note also that after the release of the FHIR DSTU2 specification, the FHIR - HAPI FHIR 4.2.0-SNAPSHOT + HAPI FHIR 4.2.0 JDK8 1.0.2 diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md index c25e2e5488d..f1b6699eb01 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md @@ -6,7 +6,7 @@ A built in parser can be used to convert HAPI FHIR Java objects into a serialize # Parsing (aka Deserializing) -As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to parse. +As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to parse. ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|parsing}} @@ -14,7 +14,7 @@ As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/ap # Encoding (aka Serializing) -As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to serialize. +As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to serialize. The following example shows a JSON Parser being used to serialize a FHIR resource. @@ -24,7 +24,7 @@ The following example shows a JSON Parser being used to serialize a FHIR resourc ## Pretty Printing -By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed outout: +By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed output: ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingPretty}} diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md index bebd8e5f40c..ef060cf9c6d 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md @@ -135,7 +135,7 @@ This will give the following output: ``` -Note that you may also "contain" resources manually in your own code if you prefer. The following example show how to do this: +Note that you may also "contain" resources manually in your own code if you prefer. The following example shows how to do this: ```java {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ResourceRefs.java|manualContained}} @@ -147,7 +147,7 @@ By default, HAPI will strip resource versions from references between resources. This is because in most circumstances, references between resources should be versionless (e.g. the reference just points to the latest version, whatever version that might be). -There are valid circumstances however for wanting versioned references. If you need HAPI to emit versionned references, you have a few options: +There are valid circumstances however for wanting versioned references. If you need HAPI to emit versioned references, you have a few options: You can force the parser to never strip versions: diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md index b07e767a353..225baded0ce 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md @@ -48,7 +48,7 @@ Note that in previous revisions of HAPI FHIR documentation we recommended using The following examples show how to use the Apache Tomcat CorsFilter to enable CORS support. The filter being used (`org.apache.catalina.filters.CorsFilter`) is bundled with Apache Tomcat so if you are deploying to that server you can use the filter. -Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for how to configure a different CORS filter, please send it our way! Examples are always useful!) +Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for configuring a different CORS filter, please send it our way! Examples are always useful!) In your web.xml file (within the WEB-INF directory in your WAR file), the following filter definition adds the CORS filter, including support for the X-FHIR-Starter header defined by SMART Platforms. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/configuration.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/configuration.md index 25125d85b08..0b33e7c4f52 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/configuration.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/configuration.md @@ -56,7 +56,7 @@ web address. A common use for logical references is in references to conformance resources, such as ValueSets, StructureDefinitions, etc. For example, you might refer to the ValueSet `http://hl7.org/fhir/ValueSet/quantity-comparator` from your own resources. In this case, you are not necessarily telling the server that this is a real address that it should resolve, but rather that this is an identifier for a ValueSet where `ValueSet.url` has the given URI/URL. -HAPI can be configured to treat certain URI/URL patterns as logical by using the DaoConfig#setTreatReferencesAsLogical property (see [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/dao/DaoConfig.html#setTreatReferencesAsLogical(java.util.Set))). +HAPI can be configured to treat certain URI/URL patterns as logical by using the DaoConfig#setTreatReferencesAsLogical property (see [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-api/ca/uhn/fhir/jpa/api/config/DaoConfig.html#setTreatReferencesAsLogical(java.util.Set))). For example: diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md new file mode 100644 index 00000000000..ae1bd0f108c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/partitioning.md @@ -0,0 +1,332 @@ +# Partitioning and Multitenancy + +HAPI FHIR 5.0.0 introduced a new feature to HAPI FHIR JPA server called **Partitioning**. + +Partitioning allows each resource on the server to be placed in a partition, which is essentially just an arbitrary identifier grouping a set of resources together. + +Partitioning is designed to be very flexible, and can be used to achieve different outcomes. For example: + +* Partitioning could be used to achieve **multitenancy**, where there are multiple logically separate pools of resources on the server. Traditionally this kind of setup is desired when each of these pools belongs to a distinct user group / organization / customer / etc. (a "tenant"), and each of these tenants should not be able to access or modify data belonging to another tenant. + +* Partitioning could also be used to **logically separate data coming from distinct sources** within an organization. For example, patient records might be placed in one partition, lab data sourced from a lab system might be placed in a second partition and patient surveys from a survey app might be placed in another. In this situation data does not need to be completely segregated (lab Observation records may have references to Patient records in the patient partition) but these partitions might be used to support security groups, retention policies, etc. + +* Partitioning could be used for **geographic sharding**, keeping data in a partition that is geographically closest to where it is likely to be used. + +These examples each have different properties in terms of security rules, and how data is organized and searched. + +# Architecture + +## Conceptual Architecture + +Partitioning in HAPI FHIR JPA means that every resource has a partition identity. This identity consists of the following attributes: + +* **Partition Name**: This is a short textual identifier for the partition that the resource belongs to. This might be a customer ID, a description of the type of data in the partition, or something else. There is no restriction on the text used aside from a maximum length of 200, but generally it makes sense to limit the text to URL-friendly characters. + +* **Partition ID**: This is an integer ID that corresponds 1:1 with the partition Name. It is used in the database as the partition identifier. + +* **Partition Date**: This is an additional partition discriminator that can be used to implement partitioning strategies using a date axis. + +Mappings between the **Partition Name** and the **Partition ID** are maintained using the [Partition Mapping Operations](#partition-mapping-operations). + +## Logical Architecture + +At the database level, partitioning involves the use of two dedicated columns to many tables within the HAPI FHIR JPA [database schema](./schema.html): + +* **PARTITION_ID** – This is an integer indicating the specific partition that a given resource is placed in. This column can also be *NULL*, meaning that the given resource is in the **Default Partition**. +* **PARTITION_DATE** – This is a date/time column that can be assigned an arbitrary value depending on your use case. Typically, this would be used for use cases where data should be automatically dropped after a certain time period using native database partition drops. + +When partitioning is used, these two columns will be populated with the same value for a given resource on all resource-specific tables (this includes [HFJ_RESOURCE](./schema.html#HFJ_RESOURCE) and all tables that have a foreign key relationship to it including [HFJ_RES_VER](./schema.html#HFJ_RES_VER), [HFJ_RESLINK](./schema.html#HFJ_RES_LINK), [HFJ_SPIDX_*](./schema.html#search-indexes), etc.) + +When a new resource is **created**, an [interceptor hook](#partition-interceptors) is invoked to request the partition ID and date to be assigned to the resource. + +When a resource is **updated**, the partition ID and date from the previous version will be used. + +When a **read operation** is being performed (e.g. a read, search, history, etc.), a separate [interceptor hook](#partition-interceptors) is invoked in order to determine whether the operation should target a specific partition. The outcome of this hook determines how the partitioning manifests itself to the end user: + +* The system can be configured to operate as a **multitenant** solution by configuring the partition interceptor to scope all read operations to read data only from the partition that request has access to.``` +* The system can be configured to operate with logical segments by configuring the partition interceptor to scope read operations to access all partitions. + +# Enabling Partitioning in HAPI FHIR + +Follow these steps to enable partitioning on the server: + +The [PartitionSettings](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html) bean contains configuration settings related to partitioning within the server. To enable partitioning, the [setPartitioningEnabled(boolean)](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setPartitioningEnabled(boolean)) property should be enabled. + +The following settings can be enabled: + +* **Include Partition in Search Hashes** ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setIncludePartitionInSearchHashes(boolean))): If this feature is enabled, partition IDs will be factored into [Search Hashes](./schema.html#search-hashes). When this flag is not set (as is the default), when a search requests a specific partition, an additional SQL WHERE predicate is added to the query to explicitly request the given partition ID. When this flag is set, this additional WHERE predicate is not necessary since the partition is factored into the hash value being searched on. Setting this flag avoids the need to manually adjust indexes against the HFJ_SPIDX tables. Note that this flag should **not be used in environments where partitioning is being used for security purposes**, since it is possible for a user to reverse engineer false hash collisions. + +* **Cross-Partition Reference Mode**: ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-jpaserver-model/ca/uhn/fhir/jpa/model/config/PartitionSettings.html#setAllowReferencesAcrossPartitions(ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMode))): This setting controls whether resources in one partition should be allowed to create references to resources in other partitions. + + +# Partition Interceptors + +In order to implement partitioning, an interceptor must be registered against the interceptor registry (either the REST Server registry, or the JPA Server registry will work). + +This interceptor can implement the hooks shown below. + +## Identify Partition for Create (Required) + +A hook against the [`Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is created in order to determine the partition the resource is assigned to. + +The criteria for determining the partition will depend on your use case. For example: + +* If you are implementing multi-tenancy the partition might be determined by using the [Request Tenant ID](/docs/server_plain/multitenancy.html). It could also be determined by looking at request headers, or the authorized user/session context, etc. + +* If you are implementing segmented data partitioning, the partition might be determined by examining the actual resource being created, by the identity of the sending system, etc. + +## Identify Partition for Read (Optional) + +A hook against the [`Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is created in order to determine the partition to assign the resource to. + +## Example: Partitioning based on Tenant ID + +The [RequestTenantPartitionInterceptor](/docs/interceptors/built_in_server_interceptors.html#request-tenant-partition-interceptor) uses the request tenant ID to determine the partition name. A simplified version of its source is shown below: + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|partitionInterceptorRequestPartition}} +``` + +## Example: Partitioning based on headers + +If requests are coming from a trusted system, that system might be relied on to determine the partition for reads and writes. + +The following example shows a simple partition interceptor that determines the partition name by looking at a custom HTTP header: + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|partitionInterceptorHeaders}} +``` + +## Example: Using Resource Contents + +When creating resources, the contents of the resource can also be factored into the decision on which tenant to use. The following example shows a very simple algorithm, placing resources into one of three partitions based on the resource type. Other contents in the resource could also be used instead. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|partitionInterceptorResourceContents}} +``` + + +# Complete Example: Using Request Tenants + +In order to achieve a multitenant configuration, the following configuration steps must be taken: + +* Partitioning must be enabled. +* A [Tenant Identification Strategy](/docs/server_plain/multitenancy.html) must be enabled on the RestfulServer. +* A [RequestTenantPartitionInterceptor](/docs/interceptors/built_in_server_interceptors.html#request-tenant-partition-interceptor) instance must be registered as an interceptor. + +Additionally, indexes will likely need to be tuned in order to support the partition-aware queries. + +The following snippet shows a server with this configuration. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PartitionExamples.java|multitenantServer}} +``` + + + +# Partition Mapping Operations + +Several operations exist that can be used to manage the existence of partitions. These operations are supplied by a [plain provider](/docs/server_plain/resource_providers.html#plain-providers) called [PartitionManagementProvider](/hapi-fhir/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.html). + +Before a partition can be used, it must be registered using these methods. + +## Creating a Partition + +The `$partition-management-add-partition` operation can be used to create a new partition. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeCardinalityDescription
        idInteger1..1 + The numeric ID for the partition. This value can be any integer, positive or negative or zero. It must not be a value that has already been used. +
        nameCode1..1 + A code (string) to assign to the partition. +
        descriptionString0..1 + An optional description for the partition. +
        + +### Example + +An HTTP POST to the following URL would be used to invoke this operation: + +```url +http://example.com/$partition-management-add-partition +``` + +The following request body could be used: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "id", + "valueInteger": 123 + }, { + "name": "name", + "valueCode": "PARTITION-123" + }, { + "name": "description", + "valueString": "a description" + } ] +} +``` + +## Updating a Partition + +The `$partition-management-update-partition` operation can be used to update an existing partition. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeCardinalityDescription
        idInteger1..1 + The numeric ID for the partition to update. This ID must already exist. +
        nameCode1..1 + A code (string) to assign to the partition. Note that it is acceptable to change the name of a partition, but this should be done with caution since partition names may be referenced by URLs, caches, etc. +
        descriptionString0..1 + An optional description for the partition. +
        + +### Example + +An HTTP POST to the following URL would be used to invoke this operation: + +```url +http://example.com/$partition-management-add-partition +``` + +The following request body could be used: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "id", + "valueInteger": 123 + }, { + "name": "name", + "valueCode": "PARTITION-123" + }, { + "name": "description", + "valueString": "a description" + } ] +} +``` + +## Deleting a Partition + +The `$partition-management-delete-partition` operation can be used to delete an existing partition. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + +
        NameTypeCardinalityDescription
        idInteger1..1 + The numeric ID for the partition to update. This ID must already exist. +
        + +### Example + +An HTTP POST to the following URL would be used to invoke this operation: + +```url +http://example.com/$partition-management-delete-partition +``` + +The following request body could be used: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "id", + "valueInteger": 123 + } ] +} +``` + + +# Limitations + +Partitioning is a relatively new feature in HAPI FHIR (added in HAPI FHIR 5.0.0) and has a number of known limitations. If you are intending to use partitioning for achieving a multi-tenant architecture it is important to consider these limitations. + +None of the limitations listed here are considered permanent. Over time the HAPI FHIR team is hoping to make all of these features partition aware. + +* **Server Capability Statement is not partition aware**: The server creates and exposes a single server capability statement, covering all partitions. This can be misleading when partitioning us used as a multitenancy strategy. + +* **Subscriptions may not be partitioned**: All subscriptions must be placed in the default partition, and subscribers will receive deliveries for any matching resources from all partitions. + +* **Conformance resources may not be partitioned**: The following resources must be placed in the default partition, and will be shared for any validation activities across all partitions: + * StructureDefinition + * Questionnaire + * ValueSet + * CodeSystem + * ConceptMap + +* **Search Parameters are not partitioned**: There is only one set of SearchParameter resources for the entire system, and any search parameters will apply to resources in all partitions. All SearchParameter resources must be stored in the default partition. + +* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md new file mode 100644 index 00000000000..68eac58c2af --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md @@ -0,0 +1,11 @@ +# Performance + +This page contains information for performance optimization. + +# Bulk Loading + +On servers where a large amount of data will be ingested, the following considerations may be helpful: + +* Optimize your database thread pool count and HTTP client thread count: Every environment will have a different optimal setting for the number of concurrent writes that are permitted, and the maximum number of database connections allowed. + +* Disable deletes: If the JPA server is configured to have the FHIR delete operation disabled, it is able to skip some resource reference deletion checks during resource creation, which can have a measurable improvement to performance over large datasets. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md new file mode 100644 index 00000000000..bdcd28b8976 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/schema.md @@ -0,0 +1,524 @@ +# HAPI FHIR JPA Schema + +**This page is a work in progress. It is not yet comprehensive.** + +It contains a description of the tables within the HAPI FHIR JPA database. Note that columns are shown using Java datatypes as opposed to SQL datatypes, because the exact SQL datatype used will vary depending on the underlying database platform. The schema creation scripts can be used to determine the underlying column types. + +# Background: Persistent IDs (PIDs) + +The HAPI FHIR JPA schema relies heavily on the concept of internal persistent IDs on tables, using a Java type of Long (8-byte integer, which translates to an *int8* or *number(19)* on various database platforms). + +Many tables use an internal persistent ID as their primary key, allowing the flexibility for other more complex business identifiers to be changed and minimizing the amount of data consumed by foreign key relationships. These persistent ID columns are generally assigned using a dedicated database sequence on platforms which support sequences. + +The persistent ID column is generally called `PID` in the database schema, although there are exceptions. + +
        + +# HFJ_RESOURCE: Resource Master Table + +Resources + +The HFJ_RESOURCE table indicates a single resource of any type in the database. For example, the resource `Patient/1` will have exactly one row in this table, representing all versions of the resource. + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameRelationshipsDatatypeNullableDescription
        PARTITION_IDIntegerNullable + This is the optional partition ID, if the resource is in a partition. See Partitioning. +
        PARTITION_DATETimestampNullable + This is the optional partition date, if the resource is in a partition. See Partitioning. +
        RES_VERLong + This is the current version ID of the resource. Will contain 1 when the resource is first + created, 2 the first time it is updated, etc. + This column is equivalent to the HFJ_RES_VER.RES_VER + column, although it does not have a foreign-key dependency in order to allow selective expunge of versions + when necessary. Not to be confused with RES_VERSION below. +
        RES_VERSIONString + This column contains the FHIR version associated with this resource, using a constant drawn + from FhirVersionEnum. + Not to be confused with RES_VER above. +
        RES_TYPEString + Contains the resource type (e.g. Patient) +
        HASH_SHA256Long + This column contains a SHA-256 hash of the current resource contents, exclusive of resource metadata. + This is used in order to detect NO-OP writes to the resource. +
        RES_PUBLISHEDTimestamp + Contains the date that the first version of the resource was created. +
        RES_UPDATEDTimestamp + Contains the date that the most recent version of the resource was created. +
        RES_DELETED_ATTimestampNullable + If the most recent version of the resource is a delete, this contains the timestamp at which + the resource was deleted. Otherwise, contains NULL. +
        + + + +
        + +# HFJ_RES_VER: Resource Versions and Contents + +The HFJ_RES_VER table contains individual versions of a resource. If the resource `Patient/1` has 3 versions, there will be 3 rows in this table. + +The complete raw contents of the resource is stored in the `RES_TEXT` column, using the encoding specified in the `RES_ENCODING` column. + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameRelationshipsDatatypeNullableDescription
        PARTITION_IDIntegerNullable + This is the optional partition ID, if the resource is in a partition. See Partitioning. +
        PARTITION_DATETimestampNullable + This is the optional partition date, if the resource is in a partition. See Partitioning. +
        PIDPKLong + This is the row persistent ID. +
        RES_IDFK to HFJ_RESOURCELong + This is the persistent ID of the resource being versioned. +
        RES_VERLong + Contains the specific version (starting with 1) of the resource that this row corresponds to. +
        RES_ENCODINGString + Describes the encoding of the resource being used to store this resource in RES_TEXT. See + Encodings below for allowable values. +
        RES_TEXTbyte[] + Contains the actual full text of the resource being stored. +
        + +## Encodings + + + + + + + + + + + + + + +
        ValueDescription
        JSONC + The resource is serialized using FHIR JSON encoding, and then compressed into a byte stream using GZIP compression. +
        + +
        + +# HFJ_FORCED_ID: Client Assigned/Visible Resource IDs + +By default, the **HFJ_RESOURCE.RES_ID** column is used as the resource ID for all server-assigned IDs. For example, if a Patient resource is created in a completely empty database, it will be assigned the ID `Patient/1` by the server and RES_ID will have a value of 1. + +However, when client-assigned IDs are used, these may contain text values to allow a client to create an ID such as `Patient/ABC`. When a client-assigned ID is given to a resource, a row is created in the **HFJ_RESOURCE** table. When an **HFJ_FORCED_ID** row exists corresponding to the equivalent **HFJ_RESOURCE** row, the RES_ID value is no longer visible or usable by FHIR clients and it becomes purely an internal ID to the JPA server. + +If the server has been configured with a [Resource Server ID Strategy](/apidocs/hapi-fhir-jpaserver-api/undefined/ca/uhn/fhir/jpa/api/config/DaoConfig.html#setResourceServerIdStrategy(ca.uhn.fhir.jpa.api.config.DaoConfig.IdStrategyEnum)) of [UUID](/apidocs/hapi-fhir-jpaserver-api/undefined/ca/uhn/fhir/jpa/api/config/DaoConfig.IdStrategyEnum.html#UUID), or the server has been configured with a [Resource Client ID Strategy](/apidocs/hapi-fhir-jpaserver-api/undefined/ca/uhn/fhir/jpa/api/config/DaoConfig.html#setResourceClientIdStrategy(ca.uhn.fhir.jpa.api.config.DaoConfig.ClientIdStrategyEnum)) of [ANY](/apidocs/hapi-fhir-jpaserver-api/undefined/ca/uhn/fhir/jpa/api/config/DaoConfig.ClientIdStrategyEnum.html#ANY) the server will create a Forced ID for all resources (not only resources having textual IDs). + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameRelationshipsDatatypeNullableDescription
        PARTITION_IDIntegerNullable + This is the optional partition ID, if the resource is in a partition. See Partitioning. +
        PARTITION_DATETimestampNullable + This is the optional partition date, if the resource is in a partition. See Partitioning. +
        PIDPKLong + This is the row persistent ID. +
        RESOURCE_PIDFK to HFJ_RESOURCELong + This is the persistent ID of the resource being versioned. +
        FORCED_IDString + Contains the specific version (starting with 1) of the resource that this row corresponds to. +
        + +
        + +# HFJ_RES_LINK: Search Links + +Resources + +When a resource is created or updated, it is indexed for searching. Any search parameters of type [Reference](http://hl7.org/fhir/search.html#reference) are resolved, and one or more rows may be created in the **HFJ_RES_LINK** table. + +## Columns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameRelationshipsDatatypeNullableDescription
        PARTITION_IDIntegerNullable + This is the optional partition ID, if the resource is in a partition. See Partitioning. + Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition + of the SOURCE resource, and not necessarily the TARGET. +
        PARTITION_DATETimestampNullable + This is the optional partition date, if the resource is in a partition. See Partitioning. + Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition + of the SOURCE resource, and not necessarily the TARGET. +
        PIDLong + Holds the persistent ID +
        SRC_PATHString + Contains the FHIRPath expression within the source resource containing the path to the target resource, as supplied by the SearchParameter resource that defined the link. +
        SRC_RESOURCE_IDLong + Contains a FK reference to the resource containing the link to the target resource. +
        TARGET_RESOURCE_IDLongNullable + Contains a FK reference to the resource that is the target resource. Will not be populated if the link contains + a reference to an external resource, or a canonical reference. +
        TARGET_RESOURCE_URLStringNullable + If this row contains a reference to an external resource or a canonical reference, this column will contain + the absolute URL. +
        SP_UPDATEDTimestamp + Contains the last updated timestamp for this row. +
        + +
        + +# Background: Search Indexes + +The HFJ_SPIDX (Search Parameter Index) tables are used to index resources for searching. When a resource is created or updated, a set of rows in these tables will be added. These are used for finding appropriate rows to return when performing FHIR searches. There are dedicated tables for supporting each of the non-reference [FHIR Search Datatypes](http://hl7.org/fhir/search.html): Date, Number, Quantity, String, Token, and URI. Note that Reference search parameters are implemented using the [HFJ_RES_LINK](#HFJ_RES_LINK) table above. + + + +## Search Hashes + +The SPIDX tables leverage "hash columns", which contain a hash of multiple columns in order to reduce index size and improve search performance. Hashes currently use the [MurmurHash3_x64_128](https://en.wikipedia.org/wiki/MurmurHash) hash algorithm, keeping only the first 64 bits in order to produce a LongInt value. + +For example, all search index tables have columns for storing the search parameter name (**SP_NAME**) and resource type (**RES_TYPE**). An additional column which hashes these two values is provided, called **HASH_IDENTITY**. + +In some configurations, the partition ID is also factored into the hashes. + +## Tables + +Search Indexes + +## Columns + +The following columns are common to **all HFJ_SPIDX_xxx tables**. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameRelationshipsDatatypeNullableDescription
        PARTITION_IDIntegerNullable + This is the optional partition ID, if the resource is in a partition. See Partitioning. + Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition + of the SOURCE resource, and not necessarily the TARGET. +
        PARTITION_DATETimestampNullable + This is the optional partition date, if the resource is in a partition. See Partitioning. + Note that the partition indicated by the PARTITION_ID and PARTITION_DATE columns refers to the partition + of the SOURCE resource, and not necessarily the TARGET. +
        SP_IDLong + Holds the persistent ID +
        RES_IDFK to HFJ_RESOURCEString + Contains the PID of the resource being indexed. +
        SP_NAMEString + This is the name of the search parameter being indexed. +
        RES_TYPEString + This is the name of the resource being indexed. +
        SP_UPDATEDTimestamp + This is the time that this row was last updated. +
        SP_MISSINGboolean + If this row represents a search parameter that is **not** populated at all in the resource being indexed, + this will be populated with the value `true`. Otherwise it will be populated with `false`. +
        + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md index a905b38c4fe..eabd07c4aa5 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md @@ -2,10 +2,10 @@ The HAPI FHIR Plain Server ([RestfulServer](/hapi-fhir/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/RestfulServer.html)) is implemented as a standard JEE Servlet, meaning that it can be deployed in any compliant JEE web container. -For users who already have an existing JAX-RS infrastructure, and who would like to use that technology foor their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html). +For users who already have an existing JAX-RS infrastructure, and who would like to use that technology for their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html).
        - The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before decid the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consiider adding it and making a pull request! + The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before deciding to use the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consider adding it and making a pull request!
        ## Features @@ -27,7 +27,7 @@ An example server can be found in the Git repo [here](https://github.com/jamesag The set-up of a JAX-RS server goes beyond the scope of this documentation. The implementation of the server follows the same pattern as the standard server. It is required to put the correct [annotation](./rest_operations.html) on the methods in the [Resource Providers](./resource_providers.html) in order to be able to call them. -Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The
        @Produces annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitely in order to avoid problems with proxy classes in a Java EE environment. +Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The @Produces annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitly in order to avoid problems with proxy classes in a Java EE environment. It is necessary to extend the abstract class [AbstractJaxRsResourceProvide](/hapi-fhir/apidocs/hapi-fhir-jaxrsserver-base/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.html). diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md similarity index 97% rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md index 58b03dbd896..82903672932 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md @@ -1,10 +1,10 @@ -# Multitenency +# Multitenancy If you wish to allow a single endpoint to support multiple tenants, you may supply the server with a multitenancy provider. This means that additional logic will be performed during request parsing to determine a tenant ID, which will be supplied to resource providers. This can be useful in servers that have multiple distinct logical pools of resources hosted on the same infrastructure. -## URL Base Multitenancy +# URL Base Multitenancy Using URL Base Multitenancy means that an additional element is added to the path of each resource between the server base URL and the resource name. For example, if your restful server is deployed to `http://acme.org:8080/baseDstu3` and a client wishes to access Patient 123 for Tenant "FOO", the resource ID (and URL to fetch that resource) would be `http://acme.org:8080/FOO/Patient/123`. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md index f72a13ca7f8..22151dc2175 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md @@ -76,7 +76,7 @@ In some cases, it may be useful to have access to the underlying HttpServletRequ # REST Exception/Error Handling -Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution you may also need to propagate errors back to the client for a variety of reasons. +Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution, you may also need to propagate errors back to the client for a variety of reasons. ## Automatic Exception Handling diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md index b66e3fac35d..240a8c484d1 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md @@ -8,7 +8,7 @@ to create your server) and the overlay drops a number of files into your project # Adding the Overlay -These instructions assume that you have an exsiting web project which uses Maven to build. The POM.xml should have a "packaging" type of "war". +These instructions assume that you have an existing web project which uses Maven to build. The POM.xml should have a "packaging" type of "war". Adding the overlay to your project is relatively simple. First, add the "hapi-fhir-testpage-overlay" dependency to the dependencies section of your POM.xml file. @@ -54,7 +54,7 @@ Then, add the following WAR plugin to the plugins section of your POM.xml ``` -Then, create a Java source file called `FhirTesterConfig.java` and copy in the following contents: +Then, create a Java source file called `FhirTesterConfig.java` and copy the following contents: diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md index c13c723a81c..550e20a73ce 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md @@ -38,6 +38,9 @@ java version "1.8.0_60" Java(TM) SE Runtime Environment (build 1.8.0_60-b27) Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode) ``` + +Individual commands can be troubleshooted by adding the `--debug` command line argument. + If this does not help, please post a question on our [Google Group](https://groups.google.com/d/forum/hapi-fhir). # Server (run-server) diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/upgrades/validation_context_430.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/upgrades/validation_context_430.md new file mode 100644 index 00000000000..756b8fed28d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/upgrades/validation_context_430.md @@ -0,0 +1,16 @@ + +### IValidationSupport Interfaces Collapsed + +Previously, a number of `IValidationSupport` interfaces existed. These interface were all almost identical but had a few minor differences relating to the different versions of FHIR. For example, the following existed, along with others: + +* org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport +* org.hl7.fhir.r4.hapi.ctx.IValidationSupport + +These have all been replaced with a single interface that is intended to be used for all versions of FHIR: + +* [ca.uhn.fhir.context.support.IContextValidationSupport](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java) + +This also means that the following classes (which previously existing in multiple packages) have been replaced with a single version supporting multiple FHIR versions. You will need to adjust package names: + +* CachingValidationSupport +* SnapshotGeneratingValidationSupport diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md new file mode 100644 index 00000000000..2e359f9ca3c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md @@ -0,0 +1,86 @@ +# Instance Validator + +HAPI provides a built-in and configurable mechanism for validating resources using . This mechanism is called the *Instance Validator*. + +The resource validator is an extendible and modular system, and you can configure it in a number of ways in order to get the specific type of validation you want to achieve. + +The validator can be manually invoked at any time by creating a validator and configuring it with one or more [IValidatorModule](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/validation/IValidatorModule.html) instances. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validationIntro}} +``` +
        + Note that in earlier releases of HAPI FHIR it was common to register different kinds of validator modules (such as [Schema/Schematron](./schema_validator.html)) because the FHIR Instance Validator module described below was not mature. This is no longer the case, and it is generally recommended to use the FHIR Instance Validator. +
        + +# FHIR Conformance Concepts + +There are a few key concepts worth explaining before getting into how validation is performed in HAPI FHIR. + +Conformance Resources: + +* [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) – Contains definitions of the valid fields in a given resource, including details about their datatypes, min/max cardinalities, valid values, and other rules about what content is valid and what is not. StructureDefinition resources are also used to express derivative profiles (e.g. a description of a constraint on a FHIR resource for a specfic purpose) as well as to describe extensions. + +* [CodeSystem](http://hl7.org/fhir/codesystem.html) – Contains definiitions of codes and vocabularies that can be used in FHIR resources, or even outside of FHIR resources. + +* [ValueSet](http://hl7.org/fhir/valueset.html) – Contains lists of codes drawn from one or more CodeSystems that are suitable for use in a specific field in a FHIR resource. + + +# FHIR Instance Validator + +
        + Note on HAPI FHIR 5.0.0+: Many of the classes described here have changed in HAPI FHIR 5.0.0 and + existing users of HAPI FHIR may need to migrate existing validation code in order to successfully use the validator + in HAPI FHIR 5.0.0 and beyond. See Migrating to 5.x for information. +
        + +HAPI has very complete support for validation against FHIR conformance resources. + +This functionality is proviided by the HAPI FHIR "reference validator", which is able +to check a resource for conformance to FHIR profiles. + +The FHIR instance validator is very powerful. It will use terminology services to validate codes, StructureDefinitions to validate semantics, and uses a customized XML/JSON parser in order to provide descriptive error messages. + +It is always worth considering the performance implications of using the Instance Validator at runtime in a production system. While efforts are made to keep the Instance Validator and its supporting infrastructure performant, the act of performing deep semantic validation is never going to be without some performance cost. + +The FHIR instance validator can be used to validate a resource against the +official structure definitions (produced by HL7) as well as against custom +definitions provided either by HL7 or by the user. + +# Running the Validator + +To execute the validator, you create a [validation support chain](./validation_support_modules.html) and pass this to an instance of [FhirInstanceValidator](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.html). The FhirInstanceValidator is then used as a module for the HAPI FHIR validation framework. + +Note that the example below uses the official FHIR StructureDefintions and ValueSets +to validate the resource. It will not work unless you include the +**hapi-fhir-validation-resources-[version].jar** module/JAR on your classpath. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|instanceValidator}} +``` + + + +# Migrating to HAPI FHIR 5.x + +HAPI FHIR 5.x contained a significant rewrite of the IValidationSupport interface and the entire validation support module infrastructure. + +Users wishing to upgrade an existing application from HAPI FHIR 4.2 or earlier may need to consider the following points (note that the HAPI FHIR JPA server has already been adapted to use the new infrastructure, so most users of the JPA server will not need to make any changes in this regard). + +* The `IContextValidationSupport` interface has been renamed to `IValidationSupport`. Previous versions of HAPI had a number of sub-interfaces of IContextValidationSupport that were all named IValidationSupport (but were FHIR version-specific and were located in distinct packages). These previous interfaces named IValidationSupport have all been removed. + +* The `IValidationSupport` interface has been reworked significantly in order to simplify extension (these points apply only to users who have created custom implementations of this interface): + + * A method called `getFhirContext()` has been added, meaning that all validation support modules must be able to report their FhirContext object. This is used to ensure that all modules in the chain are consistent with each other in terms of supported FHIR version, etc. + + * All other methods in the interface now have a default implementation which returns a null response. This means that custom implementations of this interface only need to implement the methods they care about. + + * The `validateCode(...)` methods previously passed in a special constant to `theCodeSystem` (the code system URL) parameter in cases where the system URL was implied and not explicit (e.g. when validating the `Patient.gender` field, where the resource body does not contain the code system URL). This constant was confusing to implementors and has been replaced with a new parameter of type [ConceptValidationOptions](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/ConceptValidationOptions.html) that supplies details about the validation. + +* Many classes were previously duplicated across different FHIR versions. For example, there were previously 5 classes named `DefaultProfileValidationSupport` spanning the different versions of FHIR that were supported by the validator. As of HAPI FHIR 5.x, a single [DefaultProfileValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) class exists. Users if this class (and several other implementations of the IValidationSupport interface may need to change their package declarations. + +* The DefaultProfileValidationSupport module previously contained code to perform terminology/code validation based on the CodeSystems it contained. This functionality has been relocated to a new module called [(InMemoryTerminologyServerValidationSupport)](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/InMemoryTerminologyServerValidationSupport.html), so this module should generally be added to the chain. + + + + diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/introduction.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/introduction.md index e10175c1b0a..b374e288db5 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/introduction.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/introduction.md @@ -5,10 +5,9 @@ This section contains details on several strategies for validating resources: * **[Parser Error Handler](./parser_error_handler.html)** validation is validation at runtime during the parsing of a resource. It can be used to catch input data that is impossible to fit into the HAPI data model. For example, it can be used to throw exceptions or display error messages if a resource being parsed contains elements for which there are no appropriate fields in a HAPI data structure. This is useful in order to ensure that no data is being lost during parsing, but is less comprehensive than resource validation against raw text data. Parser Validation is extremely fast and lightweight since it happens within the parser and has no dependencies to outside resources. - - -* **[Profile Validation](./profile_validator.html)** is validation of the raw or parsed resource against the official FHIR validation rules (ie. the official FHIR definitions, expressed as profile resources such as [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) and [ValueSet](http://hl7.org/fhir/valueset.html). - Profile Validation can also be used to validate resources against individual Implementation Guides which derive from the core specification (e.g. the [US Core](http://hl7.com/uscore) implementation guide). +* **[Instance Validator](./instance_validator.html)** is validation of the raw or parsed resource against the official FHIR validation rules (ie. the official FHIR definitions, expressed as profile resources such as [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) and [ValueSet](http://hl7.org/fhir/valueset.html). + + The Instance Validator can also be used to validate resources against individual Implementation Guides which derive from the core specification (e.g. the [US Core](http://hl7.com/uscore) implementation guide). -* **[Schema/Schematron Validation](./schema_validator.html)** is validation using XSD/SCH validation files provided by FHIR. This validator performs well but produces less usable error messages than Profile Validation. +* **[Schema/Schematron Validation](./schema_validator.html)** is validation using XSD/SCH validation files provided by FHIR. This validator performs well but produces less usable error messages than Profile Validation. It is considered a legacy feature, as the Instance Validator is now mature and preferred. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md deleted file mode 100644 index ebb455e92c5..00000000000 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md +++ /dev/null @@ -1,103 +0,0 @@ -# Profile Validator - -# Validator Modules - -HAPI provides a built-in and configurable mechanism for validating resources. This mechanism is called the *Resource Validator*. - -The resource validator is an extendible and modular system, and you can configure it in a number of ways in order to get the specific type of validation you want to achieve. - -The validator can be manually invoked at any time by creating a validator and configuring it with one or more [IValidatorModule](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/validation/IValidatorModule.html) instances. - -```java -{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validationIntro}} -``` - -
        - Note that in earlier releases of HAPI FHIR it was common to register different kinds of validator modules (such as [Schema/Schematron](./schema_validator.html)) because the FHIR Instance Validator module described below was not mature. This is no longer the case, and it is generally recommended to use the FHIR Instance Validator. -
        - -# FHIR Conformance Packages - -There are a few key concepts worth explaining before getting into how validation is performed in HAPI FHIR. - -Conformance Resources: - -* [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) – Contains definitions of the valid fields in a given resource, including details about their datatypes, min/max cardinalities, valid values, and other rules about what content is valid and what is not. StructureDefinition resources are also used to express derivative profiles (e.g. a description of a constraint on a FHIR resource for a specfic purpose) as well as to describe extensions. - -* [CodeSystem](http://hl7.org/fhir/codesystem.html) – Contains definiitions of codes and vocabularies that can be used in FHIR resources, or even outside of FHIR resources. - -* [ValueSet](http://hl7.org/fhir/valueset.html) – Contains lists of codes drawn from one or more CodeSystems that are suitable for use in a specific field in a FHIR resource. - - -# FHIR Instance Validator - -HAPI has very complete support for validation against FHIR conformance resources. - -This functionality is proviided by the HAPI FHIR "reference validator", which is able -to check a resource for conformance to FHIR profiles. - -The FHIR instance validator is very powerful. It will use terminology services to validate codes, StructureDefinitions to validate semantics, and uses a customized XML/JSON parser in order to provide descriptive error messages. - -It is always worth considering the performance implications of using the Instance Validator at runtime in a production system. While efforts are made to keep the Instance Validator and its supporting infrastructure performant, the act of performing deep semantic validation is never going to be without some performance cost. - -The FHIR instance validator can be used to validate a resource against the -official structure definitions (produced by HL7) as well as against custom -definitions provided either by HL7 or by the user. - -# Running the Validator - -
        - Note on FHIR Versions: Many of the classes described on this page have - multiple versions, and you should use the version of the class the is appropriate - for the version of FHIR you are looking to validate. For example, the - examples and links below are using the - org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator class to - validate FHIR R4 resources, but you would want to use the class - org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator - if you need to validate DSTU3 content. -
        - -To execute the validator, you simply create an instance of [FhirInstanceValidator](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.html) and register it to new validator, as shown in the example below. - -Note that the example below uses the official FHIR StructureDefintions and ValueSets -to validate the resource. It will not work unless you include the -**hapi-fhir-validation-resources-[version].jar** module/JAR on your classpath. - -```java -{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|instanceValidator}} -``` - -# Supplying Your Own Definitions - -The FhirInstanceValidator relies on an implementation of an interface called [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface to load StructureDefinitions, validate codes, etx. - -By default, an implementation of this interface called [DefaultProfileValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.html) is used. This implementation simply uses the built-in official FHIR definitions to validate against (and in many cases, this is good enough). - -However, if you have needs beyond simply validating against the core FHIR specification, you may wish to use something more. - -```java -{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validateSupplyProfiles}} -``` - -# Built-In Validation Support Classes - -There are a several implementations of the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface built into HAPI FHIR that can be used, typically in a chain. - -* [**DefaultProfileValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.html) - Supplies the built-in FHIR core structure definitions, including both structures and vocabulary. - -* [**ValidationSupportChain**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.html) - Can be used to chain multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. - -* [**PrePopulatedValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.html) - Contains a series of HashMaps that store loaded conformance resources in memory. Typically this is initialized at startup in order to add custom conformance resources into the chain. - -* [**PrePopulatedValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.html) - Contains a series of HashMaps that store loaded conformance resources in memory. Typically this is initialized at startup in order to add custom conformance resources into the chain. - -* [**CachingValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.html) - Caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk. - -* [**SnapshotGeneratingValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.html) - Generates StructureDefinition snapshots as needed. This should be added to your chain if you are working wiith differential StructueDefinitions that do not include the snapshot view. - - - - - - - diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md index ad4cc53a62e..4c46bb5b531 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md @@ -1,12 +1,17 @@ # Schema / Schematron Validator -FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complimentary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets. +FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complementary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets. The two sets of files are included with HAPI, and it uses them to perform validation. +
        + +The Schema/Schematron validators were recommended early in the development of FHIR itself, as the official FHIR validation toolchain was still maturing. At this time, the FHIR [Instance Validator](./instance_validator.html) is very mature, and gives far more helpful error messages than the Schema/Schematron validator is able to. For this reason, the Schema/Schematron validators are not available for validating R5+ content and may be deprecated in the future for other versions of FHIR as well. +
        + # Preparation -In order to use HAPI's Schematron support, a libaray called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.) +In order to use HAPI's Schematron support, a library called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.) Note that this library is specified as an optional dependency by HAPI FHIR so you need to explicitly include it if you want to use this functionality. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md new file mode 100644 index 00000000000..5b559f560f9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md @@ -0,0 +1,142 @@ +# Validation Support Modules + +The [Instance Validator](./instance_validator.html) relies on an implementation of an interface called [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) to load StructureDefinitions, validate codes, etc. + +By default, an implementation of this interface called [DefaultProfileValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) is used. This implementation simply uses the built-in official FHIR definitions to validate against (and in many cases, this is good enough). + +However, if you have needs beyond simply validating against the core FHIR specification, you may wish to use something more. + +# Built-In Validation Support Classes + +There are a several implementations of the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface built into HAPI FHIR that can be used, typically in a chain. + +# ValidationSupportChain + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java) + +This module can be used to combine multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. Note that nearly all methods in the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface are permitted to return `null` if they are not able to service a particular method call. So for example, if a call to the [`validateCode`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html#validateCode(ca.uhn.fhir.context.support.IValidationSupport,ca.uhn.fhir.context.support.ConceptValidationOptions,java.lang.String,java.lang.String,java.lang.String,java.lang.String)) method is made, the validator will try each module in the chain until one of them returns a non-null response. + +# DefaultProfileValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java) + +This module supplies the built-in FHIR core structure definitions, including both FHIR resource definitions (StructureDefinition resources) and FHIR built-in vocabulary (ValueSet and CodeSystem resources). + +# InMemoryTerminologyServerValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java) + +This module acts as a simple terminology service that can validate codes against ValueSet and CodeSystem resources purely in-memory (i.e. with no database). This is sufficient in many basic cases, although it is not able to validate CodeSystems with external content (i.e CodeSystems where the `CodeSystem.content` field is `external`, such as the LOINC and SNOMED CT CodeSystems). + +# PrePopulatedValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java) + +This module contains a series of HashMaps that store loaded conformance resources in memory. Typically this is initialized at startup in order to add custom conformance resources into the chain. + +# CachingValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java) + +This module caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk, but it also has value even for purely in-memory validation since validating codes against a ValueSet can require the expansion of that ValueSet. + +# SnapshotGeneratingValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java) + +This module generates StructureDefinition snapshots as needed. This should be added to your chain if you are working wiith differential StructureDefinitions that do not include the snapshot view. + +# CommonCodeSystemsTerminologyService + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java) + +This module validates codes in CodeSystems that are not distributed with the FHIR specification because they are difficult to distribute but are commonly used in FHIR resources. + +The following table lists vocabulary that is validated by this module: + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameCanonical URLsValidation Details
        USPS State Codes + ValueSet: (...)/ValueSet/us-core-usps-state +
        + CodeSystem: https://www.usps.com/ +
        + Codes are validated against a built-in list of valid state codes. +
        MimeTypes (BCP-13) + ValueSet: (...)/ValueSet/mimetypes +
        + CodeSystem: urn:ietf:bcp:13 +
        + Codes are not validated, but are instead assumed to be correct. Improved validation should be + added in the future, please get in touch if you would like to help. +
        Languages (BCP-47) + ValueSet: (...)/ValueSet/mimetypes +
        + CodeSystem: urn:ietf:bcp:47 +
        + Codes are not validated, but are instead assumed to be correct. Improved validation should be + added in the future, please get in touch if you would like to help. +
        + +# RemoteTerminologyServiceValidationSupport + +[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java) + +This module validates codes using a remote FHIR-based terminology server. + + +# Recipes + +The IValidationSupport instance passed to the FhirInstanceValidator will often resemble the chain shown in the diagram below. In this diagram: + +* DefaultProfileValidationSupport is used to supply basic built-in FHIR definitions +* PrePopulatedValidationSupport is used to supply other custom definitions +* InMemoryTerminologyServerValidationSupport is used to validate terminology +* The modules above are all added to a chain via ValidationSupportChain +* Finally, a cache is placed in front of the entire chain in order to improve performance + +Validation Support Chain
        (expand)
        + +# Recipe: Supplying Custom Definitions + +The following snippet shows how to supply custom definitions to the validator. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validateSupplyProfiles}} +``` + +# Recipe: Using a Remote Terminology Server + +The following snippet shows how to leverage a remote (FHIR-based) terminology server, by making REST calls to the external service when codes need to be validated. + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validateUsingRemoteTermSvr}} +``` + + + + + diff --git a/hapi-fhir-igpacks/pom.xml b/hapi-fhir-igpacks/pom.xml index 0a0fba57081..9cf55f8dc91 100644 --- a/hapi-fhir-igpacks/pom.xml +++ b/hapi-fhir-igpacks/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/BaseIgPackParser.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/BaseIgPackParser.java index 6f5e75aa5f3..9b8ee7f79df 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/BaseIgPackParser.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/BaseIgPackParser.java @@ -57,6 +57,10 @@ public abstract class BaseIgPackParser { myCtx = theCtx; } + public FhirContext getCtx() { + return myCtx; + } + protected abstract T createValidationSupport(Map theIgResources); private IBaseResource findResource(Map theCandidateResources, IIdType theId) { diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu2.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu2.java index e7bb6c0a2dc..5b6f54c8861 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu2.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu2.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.igpacks.parser; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -37,7 +37,7 @@ public class IgPackParserDstu2 extends BaseIgPackParser { @Override protected IValidationSupport createValidationSupport(Map theIgResources) { - return new IgPackValidationSupportDstu2(theIgResources); + return new IgPackValidationSupportDstu2(getCtx(), theIgResources); } @Override diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu3.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu3.java index 85064cae616..075646b0204 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu3.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackParserDstu3.java @@ -23,11 +23,9 @@ package ca.uhn.fhir.igpacks.parser; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Map; @@ -39,7 +37,7 @@ public class IgPackParserDstu3 extends BaseIgPackParser { @Override protected IValidationSupport createValidationSupport(Map theIgResources) { - return new IgPackValidationSupportDstu3(theIgResources); + return new IgPackValidationSupportDstu3(getCtx(), theIgResources); } @Override diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu2.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu2.java index 50f689a6b77..84d3431130e 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu2.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu2.java @@ -21,57 +21,28 @@ package ca.uhn.fhir.igpacks.parser; */ import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.dstu2.model.ConceptMap; import org.hl7.fhir.dstu2.model.StructureDefinition; import org.hl7.fhir.dstu2.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import java.util.ArrayList; -import java.util.List; import java.util.Map; public class IgPackValidationSupportDstu2 implements IValidationSupport { private final Map myIgResources; + private FhirContext myCtx; - public IgPackValidationSupportDstu2(Map theIgResources) { + public IgPackValidationSupportDstu2(FhirContext theCtx, Map theIgResources) { + myCtx = theCtx; myIgResources = theIgResources; } - @Override - public List allStructures() { - ArrayList retVal = new ArrayList<>(); - - for (Map.Entry next : myIgResources.entrySet()) { - if (next.getKey().getResourceType().equals("StructureDefinition")) { - retVal.add((StructureDefinition) next.getValue()); - } - } - return retVal; - } - - @Override - public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return null; - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { - for (Map.Entry next : myIgResources.entrySet()) { - if (next.getKey().getResourceType().equals("ValueSet")) { - ValueSet nextVs = (ValueSet) next.getValue(); - if (theSystem.equals(nextVs.getUrl())) { - return nextVs; - } - } - } - return null; - } @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + public T fetchResource(Class theClass, String theUri) { for (Map.Entry next : myIgResources.entrySet()) { if (theClass.equals(ConceptMap.class)) { if (theClass.isAssignableFrom(next.getValue().getClass())) { @@ -104,12 +75,13 @@ public class IgPackValidationSupportDstu2 implements IValidationSupport { @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { return false; } @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - return null; + public FhirContext getFhirContext() { + return myCtx; } + } diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java index 32ac3b79305..fdd68e90b2d 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java @@ -21,7 +21,8 @@ package ca.uhn.fhir.igpacks.parser; */ import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.dstu3.model.StructureDefinition; @@ -35,45 +36,27 @@ import java.util.Map; public class IgPackValidationSupportDstu3 implements IValidationSupport { private final Map myIgResources; + private FhirContext myCtx; - public IgPackValidationSupportDstu3(Map theIgResources) { + public IgPackValidationSupportDstu3(FhirContext theCtx, Map theIgResources) { myIgResources = theIgResources; + myCtx = theCtx; } - @Override - public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return null; - } @Override - public List fetchAllConformanceResources(FhirContext theContext) { + public List fetchAllConformanceResources() { return new ArrayList<>(myIgResources.values()); } @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - for (Map.Entry next : myIgResources.entrySet()) { - if (next.getKey().getResourceType().equals("StructureDefinition")) { - retVal.add((StructureDefinition) next.getValue()); - } - } - return retVal; + public ValueSet fetchValueSet(String theSystem) { + return fetchResource(ValueSet.class, theSystem); } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return fetchResource(theContext, CodeSystem.class, theSystem); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return fetchResource(theContext, ValueSet.class, theSystem); - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + public T fetchResource(Class theClass, String theUri) { for (Map.Entry next : myIgResources.entrySet()) { if (theClass.equals(CodeSystem.class)) { if (theClass.isAssignableFrom(next.getValue().getClass())) { @@ -113,27 +96,28 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport { } @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); + public StructureDefinition fetchStructureDefinition(String theUrl) { + return fetchResource(StructureDefinition.class, theUrl); } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { return false; } @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { return null; } @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { return null; } @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; + public FhirContext getFhirContext() { + return myCtx; } + } diff --git a/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java b/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java index f4ece9c3b71..1f6588d5c29 100644 --- a/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java +++ b/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java @@ -2,14 +2,15 @@ package ca.uhn.fhir.igpack.parser; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.ValueSet; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class IgPackParserDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(IgPackParserDstu3Test.class); @@ -22,8 +23,8 @@ public class IgPackParserDstu3Test { IValidationSupport result = igParser.parseIg(IgPackParserDstu3Test.class.getResourceAsStream("/us-core-stu3-validator.pack"), "US-Core STU3"); - assertNotNull(result.fetchResource(ctx, ValueSet.class, "http://hl7.org/fhir/us/core/ValueSet/simple-language")); - assertEquals(50, result.fetchAllConformanceResources(ctx).size()); + assertNotNull(result.fetchResource(ValueSet.class, "http://hl7.org/fhir/us/core/ValueSet/simple-language")); + assertEquals(50, result.fetchAllConformanceResources().size()); } } diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 34d8a852672..feb20954a3a 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -61,6 +61,11 @@ hapi-fhir-structures-hl7org-dstu2 ${project.version}
        + + ca.uhn.hapi.fhir + hapi-fhir-validation + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 @@ -96,6 +101,11 @@ hapi-fhir-jpaserver-model ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-api + ${project.version} + diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index d8d18016d9e..78eb7ef271d 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java index 10bf0e566c9..ea02fb64e91 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java @@ -119,4 +119,9 @@ public class JaxRsHttpRequest implements IHttpRequest { return ""; // TODO: can we get this from somewhere? } + @Override + public void setUri(String theUrl) { + throw new UnsupportedOperationException(); + } + } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java index c2ac1525c42..4511a914840 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java @@ -168,7 +168,7 @@ public class AbstractJaxRsResourceProviderDstu3Test { @Test public void testDeletePatient() { when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); - final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); + final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute().getOperationOutcome(); assertEquals("1", idCaptor.getValue().getIdPart()); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index fb3e1b1d911..c97eccbf8d2 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -164,7 +164,7 @@ public class AbstractJaxRsResourceProviderTest { @Test public void testDeletePatient() { when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); - final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); + final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute().getOperationOutcome(); assertEquals("1", idCaptor.getValue().getIdPart()); } diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index 1f342519db5..7807f400ef9 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-jpaserver-api/pom.xml b/hapi-fhir-jpaserver-api/pom.xml new file mode 100644 index 00000000000..a8bfc0830b1 --- /dev/null +++ b/hapi-fhir-jpaserver-api/pom.xml @@ -0,0 +1,182 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 5.0.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + hapi-fhir-jpaserver-api + jar + + HAPI FHIR JPA API + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + + + commons-logging + commons-logging + + + + + ca.uhn.hapi.fhir + hapi-fhir-server + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu2 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-r4 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-r5 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-hl7org-dstu2 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-model + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-searchparam + ${project.version} + + + org.hibernate + hibernate-core + + + xml-apis + xml-apis + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + javax.activation + activation + + + javax.activation + javax.activation-api + + + + + org.hibernate + hibernate-search-orm + + + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + xml-apis + xml-apis + + + + + com.fasterxml.jackson.core + jackson-annotations + + + org.jscience + jscience + + + + org.apache.commons + commons-collections4 + + + + org.quartz-scheduler + quartz + + + + + javax.annotation + javax.annotation-api + + + + javax.servlet + javax.servlet-api + provided + + + + + ch.qos.logback + logback-classic + test + + + org.springframework + spring-test + test + + + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + + + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + + + + + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java index 6d6262c1f1c..b79ca0c13d5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java @@ -1,10 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.config; -import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import com.google.common.annotations.VisibleForTesting; @@ -25,7 +23,7 @@ import java.util.TreeSet; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -103,7 +101,7 @@ public class DaoConfig { /** * update setter javadoc if default changes */ - private int myDeferIndexingForCodesystemsOfSize = 2000; + private int myDeferIndexingForCodesystemsOfSize = 100; private boolean myDeleteStaleSearches = true; private boolean myEnforceReferentialIntegrityOnDelete = true; private boolean myUniqueIndexesEnabled = true; @@ -184,6 +182,12 @@ public class DaoConfig { */ private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets; + /** + * @since 5.0.0 + */ + private boolean myDeleteEnabled = true; + + /** * Constructor */ @@ -393,7 +397,7 @@ public class DaoConfig { * the code system will be indexed later in an incremental process in order to * avoid overwhelming Lucene with a huge number of codes in a single operation. *

        - * Defaults to 2000 + * Defaults to 100 *

        */ public int getDeferIndexingForCodesystemsOfSize() { @@ -405,7 +409,7 @@ public class DaoConfig { * the code system will be indexed later in an incremental process in order to * avoid overwhelming Lucene with a huge number of codes in a single operation. *

        - * Defaults to 2000 + * Defaults to 100 *

        */ public void setDeferIndexingForCodesystemsOfSize(int theDeferIndexingForCodesystemsOfSize) { @@ -1112,8 +1116,6 @@ public class DaoConfig { * and other FHIR features may not behave as expected when referential integrity is not * preserved. Use this feature with caution. *

        - * - * @see CascadingDeleteInterceptor */ public boolean isEnforceReferentialIntegrityOnDelete() { return myEnforceReferentialIntegrityOnDelete; @@ -1127,8 +1129,6 @@ public class DaoConfig { * and other FHIR features may not behave as expected when referential integrity is not * preserved. Use this feature with caution. *

        - * - * @see CascadingDeleteInterceptor */ public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) { myEnforceReferentialIntegrityOnDelete = theEnforceReferentialIntegrityOnDelete; @@ -1560,28 +1560,6 @@ public class DaoConfig { myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching; } - /** - * If set to true (default is true) the server will match incoming resources against active subscriptions - * and send them to the subscription channel. If set to false no matching or sending occurs. - * - * @since 3.7.0 - */ - - public boolean isSubscriptionMatchingEnabled() { - return myModelConfig.isSubscriptionMatchingEnabled(); - } - - /** - * If set to true (default is true) the server will match incoming resources against active subscriptions - * and send them to the subscription channel. If set to false no matching or sending occurs. - * - * @since 3.7.0 - */ - - public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) { - myModelConfig.setSubscriptionMatchingEnabled(theSubscriptionMatchingEnabled); - } - public ModelConfig getModelConfig() { return myModelConfig; } @@ -1704,6 +1682,8 @@ public class DaoConfig { /** * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted * to the server matching these types will be activated. + * + * @see #addSupportedSubscriptionType(Subscription.SubscriptionChannelType) */ public Set getSupportedSubscriptionTypes() { return myModelConfig.getSupportedSubscriptionTypes(); @@ -1908,9 +1888,33 @@ public class DaoConfig { setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount())); } + /** + * This setting should be disabled (set to false) on servers that are not allowing + * deletes. Default is true. If deletes are disabled, some checks for resource + * deletion can be skipped, which improves performance. This is particularly helpful when large + * amounts of data containing client-assigned IDs are being loaded, but it can also improve + * search performance. + * + * @since 5.0.0 + */ + public void setDeleteEnabled(boolean theDeleteEnabled) { + myDeleteEnabled = theDeleteEnabled; + } + /** + * This setting should be disabled (set to false) on servers that are not allowing + * deletes. Default is true. If deletes are disabled, some checks for resource + * deletion can be skipped, which improves performance. This is particularly helpful when large + * amounts of data containing client-assigned IDs are being loaded, but it can also improve + * search performance. + * + * @since 5.0.0 + */ + public boolean isDeleteEnabled() { + return myDeleteEnabled; + } - public enum StoreMetaSourceInformationEnum { + public enum StoreMetaSourceInformationEnum { NONE(false, false), SOURCE_URI(true, false), REQUEST_ID(false, true), diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java index 140658195e1..d0c26ad60f3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.api.IDaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.apache.commons.lang3.Validate; @@ -49,7 +51,15 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { * Constructor */ public DaoRegistry() { + this(null); + } + + /** + * Constructor + */ + public DaoRegistry(FhirContext theFhirContext) { super(); + myContext = theFhirContext; } public void setSupportedResourceTypes(Collection theSupportedResourceTypes) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java index 2c106b38565..bc9f9e12396 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java @@ -1,18 +1,16 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity; import ca.uhn.fhir.jpa.model.entity.ResourceTag; -import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.Collection; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -43,14 +41,8 @@ public interface IDao { FhirContext getContext(); - /** - * Populate all of the runtime dependencies that a bundle provider requires in order to work - */ - void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider); - IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation); R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation); - ISearchParamRegistry getSearchParamRegistry(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java index 0f396de735a..f6893a810e0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -21,14 +21,16 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -41,8 +43,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletResponse; import java.util.Date; @@ -201,7 +201,6 @@ public interface IFhirResourceDao extends IDao { IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails); - @Transactional(propagation = Propagation.SUPPORTS) IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse); Set searchForIds(SearchParameterMap theParams, RequestDetails theRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoCodeSystem.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java similarity index 88% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoCodeSystem.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java index 3c167e59e2d..ba957e00542 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoCodeSystem.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java @@ -1,7 +1,7 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.util.ParametersUtil; import org.hl7.fhir.instance.model.api.IBaseParameters; @@ -15,7 +15,7 @@ import java.util.List; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -38,7 +38,7 @@ public interface IFhirResourceDaoCodeSystem ext List findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest); @Nonnull - IContextValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, CD theCoding, RequestDetails theRequestDetails); + IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, CD theCoding, RequestDetails theRequestDetails); SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, CD theCodingA, CD theCodingB, RequestDetails theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoComposition.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoComposition.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoComposition.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoComposition.java index 08a7f77279f..cabe7d62ef0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoComposition.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoComposition.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -12,7 +12,7 @@ import javax.servlet.http.HttpServletRequest; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoConceptMap.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoConceptMap.java similarity index 85% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoConceptMap.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoConceptMap.java index 2f16a132b4c..04f7f53dfad 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoConceptMap.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoConceptMap.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoEncounter.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoEncounter.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java index 3bc801ed932..b94396b1495 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoEncounter.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java @@ -1,10 +1,10 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import javax.servlet.http.HttpServletRequest; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoMessageHeader.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoMessageHeader.java index 512369d7db3..691fd063459 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoMessageHeader.java @@ -1,10 +1,10 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import org.hl7.fhir.instance.model.api.IBaseResource; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoPatient.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoPatient.java index 500de64b535..57fbe535d24 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoPatient.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoPatient.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -13,7 +13,7 @@ import javax.servlet.http.HttpServletRequest; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSearchParameter.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSearchParameter.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSearchParameter.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSearchParameter.java index 964826a5209..a8d3f12e55b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSearchParameter.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSearchParameter.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoStructureDefinition.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoStructureDefinition.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoStructureDefinition.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoStructureDefinition.java index 306cb4b1d49..7864e3310cb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoStructureDefinition.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoStructureDefinition.java @@ -1,10 +1,10 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import org.hl7.fhir.instance.model.api.IBaseResource; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSubscription.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSubscription.java index a76d894278c..f2f94f30b82 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoSubscription.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -6,7 +6,7 @@ import org.hl7.fhir.instance.model.api.IIdType; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java index a186412745d..fa30e3576de 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirSystemDao.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirSystemDao.java index 38d841b6629..b1d43426689 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirSystemDao.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseBundle; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java index df636d7a560..f9ac5d9bd10 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyCurrentlyReindexing.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java similarity index 95% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyCurrentlyReindexing.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java index f0ecd74f0f3..c5778090d84 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyCurrentlyReindexing.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ResourceMetadataKeySupportingAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyResourcePid.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyResourcePid.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyResourcePid.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyResourcePid.java index 4718eff4889..4c39cad0785 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MetadataKeyResourcePid.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyResourcePid.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.dao; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,11 +20,9 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import org.hl7.fhir.instance.model.api.IAnyResource; - import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ResourceMetadataKeySupportingAnyResource; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IAnyResource; public final class MetadataKeyResourcePid extends ResourceMetadataKeySupportingAnyResource { private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java index b1f0028314f..4a9247efb38 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.api.MethodOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/DeleteConflict.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflict.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/DeleteConflict.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflict.java index 81c902f4f0f..b53bcd46004 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/DeleteConflict.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflict.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.util; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflictList.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflictList.java index bd5eb74bf92..6aaf63547de 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteConflictList.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.delete; +package ca.uhn.fhir.jpa.api.model; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.delete; * #L% */ -import ca.uhn.fhir.jpa.util.DeleteConflict; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.util.Assert; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java similarity index 95% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java index 8b59cc6efe9..e1ab49e8263 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOptions.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOptions.java index f7a63fbe8e0..162fd114658 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOptions.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.util; +package ca.uhn.fhir.jpa.api.model; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOutcome.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOutcome.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOutcome.java index 28436605768..757a2ecf335 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOutcome.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/ExpungeOutcome.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.util; +package ca.uhn.fhir.jpa.api.model; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationMatch.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationMatch.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationMatch.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationMatch.java index 800901a21ea..a52421a0916 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationMatch.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationMatch.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationQuery.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationQuery.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationQuery.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationQuery.java index b3af07e132d..6aefcf9ae3a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationQuery.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationQuery.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationRequest.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationRequest.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationRequest.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationRequest.java index 68fd74d406d..19d6388b96c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationRequest.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationRequest.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationResult.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationResult.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationResult.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationResult.java index f6ea673eef9..8e305b241ad 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TranslationResult.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/TranslationResult.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.api.model; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/WarmCacheEntry.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/WarmCacheEntry.java index d690770c360..81411ac4cf8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/WarmCacheEntry.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/WarmCacheEntry.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.search.warm; +package ca.uhn.fhir.jpa.api.model; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ResourceProviderFactory.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java similarity index 95% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ResourceProviderFactory.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java index 3344bb2c0da..5e43727ddb0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ResourceProviderFactory.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.util; +package ca.uhn.fhir.jpa.api.rp; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/svc/ISearchCoordinatorSvc.java similarity index 81% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java rename to hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/svc/ISearchCoordinatorSvc.java index c7e983558b1..671a4dbef0b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/svc/ISearchCoordinatorSvc.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.search; +package ca.uhn.fhir.jpa.api.svc; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR JPA API * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.search; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.CacheControlDirective; @@ -37,7 +37,7 @@ public interface ISearchCoordinatorSvc { List getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails); - IBundleProvider registerSearch(IFhirResourceDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails); + IBundleProvider registerSearch(IFhirResourceDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails); /** * Fetch the total number of search results for the given currently executing search, if one is currently executing and diff --git a/hapi-fhir-jpaserver-example/src/test/resources/.keep_hapi-fhir-jpaserver-example b/hapi-fhir-jpaserver-api/src/main/resources/.keep-jpaserver-api similarity index 100% rename from hapi-fhir-jpaserver-example/src/test/resources/.keep_hapi-fhir-jpaserver-example rename to hapi-fhir-jpaserver-api/src/main/resources/.keep-jpaserver-api diff --git a/hapi-fhir-osgi-core/src/test/resources/.keep b/hapi-fhir-jpaserver-api/src/test/java/.keep similarity index 100% rename from hapi-fhir-osgi-core/src/test/resources/.keep rename to hapi-fhir-jpaserver-api/src/test/java/.keep diff --git a/hapi-fhir-jpaserver-api/src/test/resources/.keep b/hapi-fhir-jpaserver-api/src/test/resources/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 00fd60b7ff5..fb6a4dc950c 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -48,6 +48,11 @@ commons-csv
        + + co.elastic.apm + apm-agent-api + + ca.uhn.hapi.fhir hapi-fhir-base @@ -236,18 +241,6 @@ javax.annotation javax.annotation-api - - - - - @@ -255,11 +248,6 @@ derby test - org.apache.derby derbytools @@ -319,15 +307,7 @@ - - - org.springframework spring-core @@ -370,12 +350,6 @@ org.springframework spring-messaging - org.springframework spring-tx @@ -422,6 +396,16 @@ + + org.hibernate + hibernate-java8 + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + org.hibernate hibernate-ehcache @@ -433,7 +417,7 @@ - org.hibernate + org.hibernate.validator hibernate-validator @@ -451,10 +435,6 @@ javax.activation 1.2.0 - - - - javax.mail javax.mail-api @@ -471,14 +451,6 @@ - - - - - javax.el javax.el-api diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java index 3a6d6e956af..be047d6f3df 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java @@ -22,9 +22,9 @@ package ca.uhn.fhir.jpa.binstore; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -40,7 +40,14 @@ import ca.uhn.fhir.util.DateUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.ICompositeType; +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.factory.annotation.Autowired; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/StoredDetails.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/StoredDetails.java index 6b892356268..276dcd2b709 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/StoredDetails.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/StoredDetails.java @@ -22,8 +22,7 @@ package ca.uhn.fhir.jpa.binstore; import ca.uhn.fhir.jpa.util.JsonDateDeserializer; import ca.uhn.fhir.jpa.util.JsonDateSerializer; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; +import ca.uhn.fhir.model.api.IModelJson; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -33,9 +32,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import javax.annotation.Nonnull; import java.util.Date; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public class StoredDetails { +public class StoredDetails implements IModelJson { @JsonProperty("blobId") private String myBlobId; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java index 5800362ed58..4c5547fc7cc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java @@ -21,7 +21,12 @@ package ca.uhn.fhir.jpa.bulk; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.dao.IResultIterator; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.dao.data.IBulkExportCollectionDao; import ca.uhn.fhir.jpa.dao.data.IBulkExportCollectionFileDao; import ca.uhn.fhir.jpa.dao.data.IBulkExportJobDao; @@ -35,7 +40,6 @@ import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.param.DateRangeParam; @@ -65,7 +69,13 @@ import javax.transaction.Transactional; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -221,7 +231,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc { map.setLastUpdated(new DateRangeParam(job.getSince(), null)); } - IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null); + IResultIterator resultIterator = sb.createQuery(map, new SearchRuntimeDetails(null, theJobUuid), null, null); storeResultsToFiles(nextCollection, sb, resultIterator, jobResourceCounter, jobStopwatch); } @@ -347,14 +357,14 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc { requestBuilder.append("&").append(JpaConstants.PARAM_EXPORT_SINCE).append("=").append(new InstantType(since).setTimeZoneZulu(true).getValueAsString()); } if (theFilters != null && theFilters.size() > 0) { - requestBuilder.append("&").append(JpaConstants.PARAM_EXPORT_TYPE).append("=").append(String.join(",", theFilters)); + requestBuilder.append("&").append(JpaConstants.PARAM_EXPORT_TYPE_FILTER).append("=").append(String.join(",", theFilters)); } String request = requestBuilder.toString(); Date cutoff = DateUtils.addMilliseconds(new Date(), -myReuseBulkExportForMillis); Pageable page = PageRequest.of(0, 10); Slice existing = myBulkExportJobDao.findExistingJob(page, request, cutoff, BulkJobStatusEnum.ERROR); - if (existing.isEmpty() == false) { + if (!existing.isEmpty()) { return toSubmittedJobInfo(existing.iterator().next()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkExportResponseJson.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkExportResponseJson.java index fa44e1c06e8..23e254a6a91 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkExportResponseJson.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkExportResponseJson.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.bulk; import ca.uhn.fhir.jpa.util.JsonDateDeserializer; import ca.uhn.fhir.jpa.util.JsonDateSerializer; +import ca.uhn.fhir.model.api.IModelJson; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -98,9 +99,7 @@ public class BulkExportResponseJson { return retVal; } - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) - public static class Output { + public static class Output implements IModelJson { @JsonProperty("type") private String myType; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index 46be7d7b1e2..06379bf1d55 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -4,21 +4,33 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.executor.InterceptorService; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider; import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver; +import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.graphql.JpaStorageServices; import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService; +import ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl; +import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; +import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory; import ca.uhn.fhir.jpa.sched.HapiSchedulerServiceImpl; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory; +import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider; +import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl; import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl; @@ -26,21 +38,22 @@ import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; -import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribableChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscribableChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; +import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices; +import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; import org.springframework.core.env.Environment; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; @@ -79,12 +92,20 @@ import org.springframework.web.socket.config.annotation.WebSocketConfigurer; @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.*")}) + @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.subscription.*"), + @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.searchparam.*") +}) +@Import({ + SearchParamConfig.class +}) public abstract class BaseConfig { + public static final String JPA_VALIDATION_SUPPORT_CHAIN = "myJpaValidationSupportChain"; public static final String TASK_EXECUTOR_NAME = "hapiJpaTaskExecutor"; public static final String GRAPHQL_PROVIDER_NAME = "myGraphQLProvider"; private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI"; + public static final String PERSISTED_JPA_BUNDLE_PROVIDER = "PersistedJpaBundleProvider"; + public static final String PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER = "PersistedJpaSearchFirstPageBundleProvider"; @Autowired protected Environment myEnv; @@ -126,23 +147,12 @@ public abstract class BaseConfig { return b; } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryImpl(); - } - - @Bean(name = "mySubscriptionTriggeringProvider") @Lazy public SubscriptionTriggeringProvider subscriptionTriggeringProvider() { return new SubscriptionTriggeringProvider(); } - @Bean - public SubscriptionActivatingInterceptor subscriptionActivatingInterceptor() { - return new SubscriptionActivatingInterceptor(); - } - @Bean(name = "myAttachmentBinaryAccessProvider") @Lazy public BinaryAccessProvider binaryAccessProvider() { @@ -155,6 +165,12 @@ public abstract class BaseConfig { return new BinaryStorageInterceptor(); } + @Bean + @Primary + public IResourceLinkResolver daoResourceLinkResolver() { + return new DaoResourceLinkResolver(); + } + @Bean public ISearchCacheSvc searchCacheSvc() { return new DatabaseSearchCacheSvcImpl(); @@ -191,40 +207,16 @@ public abstract class BaseConfig { return new StaleSearchDeletingSvcImpl(); } - @Bean - public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() { - return new InMemorySubscriptionMatcher(); - } - - @Bean - public DaoSubscriptionMatcher daoSubscriptionMatcher() { - return new DaoSubscriptionMatcher(); - } - - /** - * Create a @Primary @Bean if you need a different implementation - */ - @Bean - public ISubscribableChannelFactory subscribableChannelFactory() { - return new LinkedBlockingQueueSubscribableChannelFactory(); - } - - @Bean - public SubscriptionChannelFactory subscriptionChannelFactory() { - return new SubscriptionChannelFactory(); - } - - @Bean - @Primary - public ISubscriptionMatcher subscriptionMatcherCompositeInMemoryDatabase() { - return new CompositeInMemoryDaoSubscriptionMatcher(daoSubscriptionMatcher(), inMemorySubscriptionMatcher()); - } - @Bean public HapiFhirHibernateJpaDialect hibernateJpaDialect() { return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer()); } + @Bean + public IRequestPartitionHelperService requestPartitionHelperService() { + return new RequestPartitionHelperService(); + } + @Bean public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() { return new PersistenceExceptionTranslationPostProcessor(); @@ -247,11 +239,28 @@ public abstract class BaseConfig { return new JpaConsentContextServices(); } + @Bean + @Lazy + public IPartitionLookupSvc partitionConfigSvc() { + return new PartitionLookupSvcImpl(); + } + + @Bean + @Lazy + public PartitionManagementProvider partitionManagementProvider() { + return new PartitionManagementProvider(); + } + + @Bean + @Lazy + public RequestTenantPartitionInterceptor requestTenantPartitionInterceptor() { + return new RequestTenantPartitionInterceptor(); + } + @Bean @Lazy public TerminologyUploaderProvider terminologyUploaderProvider() { - TerminologyUploaderProvider retVal = new TerminologyUploaderProvider(); - return retVal; + return new TerminologyUploaderProvider(); } @Bean @@ -277,6 +286,23 @@ public abstract class BaseConfig { } + @Bean + public PersistedJpaBundleProviderFactory persistedJpaBundleProviderFactory() { + return new PersistedJpaBundleProviderFactory(); + } + + @Bean(name= PERSISTED_JPA_BUNDLE_PROVIDER) + @Scope("prototype") + public PersistedJpaBundleProvider persistedJpaBundleProvider(RequestDetails theRequest, String theUuid) { + return new PersistedJpaBundleProvider(theRequest, theUuid); + } + + @Bean(name= PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER) + @Scope("prototype") + public PersistedJpaSearchFirstPageBundleProvider persistedJpaSearchFirstPageBundleProvider(RequestDetails theRequest, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theSearchTask, ISearchBuilder theSearchBuilder) { + return new PersistedJpaSearchFirstPageBundleProvider(theSearch, theSearchTask, theSearchBuilder, theRequest); + } + public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer())); theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java index a63594bf83c..9dd3ebe2c29 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java @@ -20,15 +20,27 @@ package ca.uhn.fhir.jpa.config; * #L% */ +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; +import ca.uhn.fhir.validation.IInstanceValidatorModule; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.r5.utils.IResourceValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; @Configuration public abstract class BaseConfigDstu3Plus extends BaseConfig { @@ -51,4 +63,37 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig { @Bean public abstract ITermVersionAdapterSvc terminologyVersionAdapterSvc(); + @Bean(name = "myDefaultProfileValidationSupport") + public IValidationSupport defaultProfileValidationSupport() { + return new DefaultProfileValidationSupport(fhirContext()); + } + + @Bean(name = JPA_VALIDATION_SUPPORT_CHAIN) + public ValidationSupportChain jpaValidationSupportChain() { + return new JpaValidationSupportChain(fhirContext()); + } + + @Bean(name = "myJpaValidationSupport") + public IValidationSupport jpaValidationSupport() { + return new JpaPersistedResourceValidationSupport(fhirContext()); + } + + @Primary + @Bean() + public IValidationSupport validationSupportChain() { + return new CachingValidationSupport(jpaValidationSupportChain()); + } + + @Bean(name = "myInstanceValidator") + @Lazy + public IInstanceValidatorModule instanceValidator() { + FhirInstanceValidator val = new FhirInstanceValidator(fhirContext()); + val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); + val.setValidationSupport(validationSupportChain()); + return val; + } + + + @Bean + public abstract ITermReadSvc terminologyService(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index 95fddc3e65e..8e85e7e02ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -1,20 +1,26 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; import ca.uhn.fhir.jpa.term.TermReadSvcDstu2; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.dstu2.composite.MetaDt; +import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.validation.IInstanceValidatorModule; import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.instance.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; -import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.HapiToHl7OrgDstu2ValidatingSupportWrapper; import org.hl7.fhir.r5.utils.IResourceValidator; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; @@ -79,18 +85,29 @@ public class BaseDstu2Config extends BaseConfig { return ourFhirContextDstu2Hl7Org; } - @Bean(name = "myInstanceValidatorDstu2") + @Bean(name = "myInstanceValidator") @Lazy - public IInstanceValidatorModule instanceValidatorDstu2() { - FhirInstanceValidator retVal = new FhirInstanceValidator(); + public IInstanceValidatorModule instanceValidator() { + ValidationSupportChain validationSupportChain = validationSupportChain(); + CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(new HapiToHl7OrgDstu2ValidatingSupportWrapper(validationSupportChain)); + FhirInstanceValidator retVal = new FhirInstanceValidator(cachingValidationSupport); retVal.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); - retVal.setValidationSupport(new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), jpaValidationSupportDstu2()))); return retVal; } - @Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME) - public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() { - ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2(); + @Bean(name = JPA_VALIDATION_SUPPORT_CHAIN) + public ValidationSupportChain validationSupportChain() { + DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(fhirContext()); + InMemoryTerminologyServerValidationSupport inMemoryTerminologyServer = new InMemoryTerminologyServerValidationSupport(fhirContextDstu2()); + IValidationSupport jpaValidationSupport = jpaValidationSupportDstu2(); + CommonCodeSystemsTerminologyService commonCodeSystemsTermSvc = new CommonCodeSystemsTerminologyService(fhirContext()); + return new ValidationSupportChain(defaultProfileValidationSupport, jpaValidationSupport, inMemoryTerminologyServer, commonCodeSystemsTermSvc); + } + + @Primary + @Bean + public IValidationSupport jpaValidationSupportDstu2() { + JpaPersistedResourceValidationSupport retVal = new JpaPersistedResourceValidationSupport(fhirContextDstu2()); return retVal; } @@ -107,13 +124,8 @@ public class BaseDstu2Config extends BaseConfig { return searchDao; } - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu2 searchParamExtractor() { - return new SearchParamExtractorDstu2(); - } - @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) - public IFhirSystemDao systemDaoDstu2() { + public IFhirSystemDao systemDaoDstu2() { ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2(); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 932344827ed..13e19195641 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.config.dstu3; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3; @@ -17,14 +17,9 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; -import ca.uhn.fhir.validation.IInstanceValidatorModule; import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Meta; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -81,7 +76,7 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { @Bean(name = GRAPHQL_PROVIDER_NAME) @Lazy public GraphQLProvider graphQLProvider() { - return new GraphQLProvider(fhirContextDstu3(), validationSupportChainDstu3(), graphqlStorageServices()); + return new GraphQLProvider(fhirContextDstu3(), validationSupportChain(), graphqlStorageServices()); } @Bean @@ -94,30 +89,6 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { return new TransactionProcessor(); } - @Bean(name = "myInstanceValidatorDstu3") - @Lazy - public IInstanceValidatorModule instanceValidatorDstu3() { - FhirInstanceValidator val = new FhirInstanceValidator(); - val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); - val.setValidationSupport(validationSupportChainDstu3()); - return val; - } - - @Bean - public DefaultProfileValidationSupport defaultProfileValidationSupport() { - return new DefaultProfileValidationSupport(); - } - - @Bean - public JpaValidationSupportChainDstu3 jpaValidationSupportChain() { - return new JpaValidationSupportChainDstu3(); - } - - @Bean(name = "myJpaValidationSupportDstu3") - public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 jpaValidationSupportDstu3() { - return new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3(); - } - @Bean(name = "myResourceCountsCache") public ResourceCountCache resourceCountsCache() { ResourceCountCache retVal = new ResourceCountCache(() -> systemDaoDstu3().getResourceCounts()); @@ -130,13 +101,8 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { return new FulltextSearchSvcImpl(); } - @Bean - public SearchParamExtractorDstu3 searchParamExtractor() { - return new SearchParamExtractorDstu3(); - } - @Bean(name = "mySystemDaoDstu3") - public IFhirSystemDao systemDaoDstu3() { + public IFhirSystemDao systemDaoDstu3() { return new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); } @@ -153,15 +119,10 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { return new TermLoaderSvcImpl(); } + @Override @Bean public ITermReadSvcDstu3 terminologyService() { return new TermReadSvcDstu3(); } - @Primary - @Bean(name = "myJpaValidationSupportChainDstu3") - public IValidationSupport validationSupportChainDstu3() { - return new CachingValidationSupport(jpaValidationSupportChain()); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 67038250185..bf53a64685f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.config.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4; @@ -17,14 +17,9 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; -import ca.uhn.fhir.validation.IInstanceValidatorModule; import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Meta; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -92,33 +87,7 @@ public class BaseR4Config extends BaseConfigDstu3Plus { @Bean(name = GRAPHQL_PROVIDER_NAME) @Lazy public GraphQLProvider graphQLProvider() { - return new GraphQLProvider(fhirContextR4(), validationSupportChainR4(), graphqlStorageServices()); - } - - @Bean(name = "myInstanceValidatorR4") - @Lazy - public IInstanceValidatorModule instanceValidatorR4() { - FhirInstanceValidator val = new FhirInstanceValidator(); - IResourceValidator.BestPracticeWarningLevel level = IResourceValidator.BestPracticeWarningLevel.Warning; - val.setBestPracticeWarningLevel(level); - val.setValidationSupport(validationSupportChainR4()); - return val; - } - - @Bean - public DefaultProfileValidationSupport defaultProfileValidationSupport() { - return new DefaultProfileValidationSupport(); - } - - @Bean - public JpaValidationSupportChainR4 jpaValidationSupportChain() { - return new JpaValidationSupportChainR4(); - } - - @Bean(name = "myJpaValidationSupportR4", autowire = Autowire.BY_NAME) - public ca.uhn.fhir.jpa.dao.r4.IJpaValidationSupportR4 jpaValidationSupportR4() { - ca.uhn.fhir.jpa.dao.r4.JpaValidationSupportR4 retVal = new ca.uhn.fhir.jpa.dao.r4.JpaValidationSupportR4(); - return retVal; + return new GraphQLProvider(fhirContextR4(), validationSupportChain(), graphqlStorageServices()); } @Bean(name = "myResourceCountsCache") @@ -134,13 +103,8 @@ public class BaseR4Config extends BaseConfigDstu3Plus { return searchDao; } - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorR4 searchParamExtractor() { - return new SearchParamExtractorR4(); - } - @Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME) - public IFhirSystemDao systemDaoR4() { + public IFhirSystemDao systemDaoR4() { ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4 retVal = new ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4(); return retVal; } @@ -158,15 +122,10 @@ public class BaseR4Config extends BaseConfigDstu3Plus { return new TermLoaderSvcImpl(); } + @Override @Bean(autowire = Autowire.BY_TYPE) public ITermReadSvcR4 terminologyService() { return new TermReadSvcR4(); } - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainR4") - public IValidationSupport validationSupportChainR4() { - return new CachingValidationSupport(jpaValidationSupportChain()); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java index 484a90f8431..78b6327e92d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.config.r5; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5; @@ -17,15 +17,9 @@ import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5; -import ca.uhn.fhir.validation.IInstanceValidatorModule; import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r5.model.Bundle; -import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r5.model.Meta; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -93,33 +87,7 @@ public class BaseR5Config extends BaseConfigDstu3Plus { @Bean(name = GRAPHQL_PROVIDER_NAME) @Lazy public GraphQLProvider graphQLProvider() { - return new GraphQLProvider(fhirContextR5(), validationSupportChainR5(), graphqlStorageServices()); - } - - @Bean(name = "myInstanceValidatorR5") - @Lazy - public IInstanceValidatorModule instanceValidatorR5() { - FhirInstanceValidator val = new FhirInstanceValidator(); - IResourceValidator.BestPracticeWarningLevel level = IResourceValidator.BestPracticeWarningLevel.Warning; - val.setBestPracticeWarningLevel(level); - val.setValidationSupport(validationSupportChainR5()); - return val; - } - - @Bean - public DefaultProfileValidationSupport defaultProfileValidationSupport() { - return new DefaultProfileValidationSupport(); - } - - @Bean - public JpaValidationSupportChainR5 jpaValidationSupportChain() { - return new JpaValidationSupportChainR5(); - } - - @Bean(name = "myJpaValidationSupportR5", autowire = Autowire.BY_NAME) - public ca.uhn.fhir.jpa.dao.r5.IJpaValidationSupportR5 jpaValidationSupportR5() { - ca.uhn.fhir.jpa.dao.r5.JpaValidationSupportR5 retVal = new ca.uhn.fhir.jpa.dao.r5.JpaValidationSupportR5(); - return retVal; + return new GraphQLProvider(fhirContextR5(), validationSupportChain(), graphqlStorageServices()); } @Bean(name = "myResourceCountsCache") @@ -135,13 +103,8 @@ public class BaseR5Config extends BaseConfigDstu3Plus { return searchDao; } - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorR5 searchParamExtractor() { - return new SearchParamExtractorR5(); - } - @Bean(name = "mySystemDaoR5", autowire = Autowire.BY_NAME) - public IFhirSystemDao systemDaoR5() { + public IFhirSystemDao systemDaoR5() { ca.uhn.fhir.jpa.dao.r5.FhirSystemDaoR5 retVal = new ca.uhn.fhir.jpa.dao.r5.FhirSystemDaoR5(); return retVal; } @@ -159,15 +122,10 @@ public class BaseR5Config extends BaseConfigDstu3Plus { return new TermLoaderSvcImpl(); } + @Override @Bean(autowire = Autowire.BY_TYPE) public ITermReadSvcR5 terminologyService() { return new TermReadSvcR5(); } - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainR5") - public IValidationSupport validationSupportChainR5() { - return new CachingValidationSupport(jpaValidationSupportChain()); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 1b14c098e87..4892135426e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1,10 +1,26 @@ package ca.uhn.fhir.jpa.dao; -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IDao; +import ca.uhn.fhir.jpa.api.dao.IJpaDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceProvenanceDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.expunge.ExpungeService; import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer; import ca.uhn.fhir.jpa.dao.index.IdHelperService; @@ -13,18 +29,17 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.entity.ResourceSearchView; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.AddRemoveCount; @@ -75,7 +90,11 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.PostConstruct; -import javax.persistence.*; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; @@ -84,7 +103,12 @@ import javax.xml.stream.events.XMLEvent; import java.util.*; import java.util.Map.Entry; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.trim; /* * #%L @@ -165,6 +189,8 @@ public abstract class BaseHapiFhirDao extends BaseStora private SearchBuilderFactory mySearchBuilderFactory; private FhirContext myContext; private ApplicationContext myApplicationContext; + @Autowired + private PartitionSettings myPartitionSettings; @Override protected IInterceptorBroadcaster getInterceptorBroadcaster() { @@ -199,6 +225,7 @@ public abstract class BaseHapiFhirDao extends BaseStora retVal.setResourceType(theEntity.getResourceType()); retVal.setForcedId(theId.getIdPart()); retVal.setResource(theEntity); + retVal.setPartitionId(theEntity.getPartitionId()); theEntity.setForcedId(retVal); } } @@ -392,9 +419,13 @@ public abstract class BaseHapiFhirDao extends BaseStora search = mySearchCacheSvc.save(search); - return new PersistedJpaBundleProvider(theRequest, search.getUuid(), this, mySearchBuilderFactory); + return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search.getUuid()); } + @Autowired + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; + + void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) { String newVersion; long newVersionLong; @@ -412,16 +443,6 @@ public abstract class BaseHapiFhirDao extends BaseStora theSavedEntity.setVersion(newVersionLong); } - @Override - public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) { - theProvider.setContext(getContext()); - theProvider.setEntityManager(myEntityManager); - theProvider.setPlatformTransactionManager(myPlatformTransactionManager); - theProvider.setSearchCacheSvc(mySearchCacheSvc); - theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc); - theProvider.setInterceptorBroadcaster(myInterceptorBroadcaster); - } - public boolean isLogicalReference(IIdType theId) { return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId); } @@ -617,6 +638,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } } + return retVal; } @@ -678,6 +700,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } } } + return retVal; } @@ -853,8 +876,9 @@ public abstract class BaseHapiFhirDao extends BaseStora // 4. parse the text to FHIR R retVal; if (resourceEncoding != ResourceEncodingEnum.DEL) { - IParser parser = resourceEncoding.newParser(getContext(theEntity.getFhirVersion())); - parser.setParserErrorHandler(new LenientErrorHandler(false).setErrorOnInvalidValue(false)); + + LenientErrorHandler errorHandler = new LenientErrorHandler(false).setErrorOnInvalidValue(false); + IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), errorHandler); try { retVal = parser.parseResource(resourceType, resourceText); @@ -1072,6 +1096,7 @@ public abstract class BaseHapiFhirDao extends BaseStora ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity(); provenance.setResourceHistoryTable(historyEntry); provenance.setResourceTable(entity); + provenance.setPartitionId(entity.getPartitionId()); if (haveRequestId) { provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH)); } @@ -1146,7 +1171,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } } - // Syncrhonize composite params + // Synchronize composite params mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams); } } @@ -1362,11 +1387,6 @@ public abstract class BaseHapiFhirDao extends BaseStora } - @Override - public ISearchParamRegistry getSearchParamRegistry() { - return mySearchParamRegistry; - } - @PostConstruct public void start() { // nothing yet diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 6c8407107fe..45d07ea8fa0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -24,34 +24,77 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.jpa.delete.DeleteConflictService; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.BaseHasResource; +import ca.uhn.fhir.jpa.model.entity.BaseTag; +import ca.uhn.fhir.jpa.model.entity.ForcedId; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.TagDefinition; +import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.*; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PatchTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.ValidationModeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.StopWatch; -import ca.uhn.fhir.validation.*; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.IInstanceValidatorModule; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ValidationOptions; +import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; @@ -67,7 +110,14 @@ import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -94,6 +144,10 @@ public abstract class BaseHapiFhirResourceDao extends B private IInstanceValidatorModule myInstanceValidator; private String myResourceName; private Class myResourceType; + @Autowired + private IRequestPartitionHelperService myRequestPartitionHelperService; + @Autowired + private PartitionSettings myPartitionSettings; @Override public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) { @@ -159,9 +213,11 @@ public abstract class BaseHapiFhirResourceDao extends B if (myDaoConfig.getResourceServerIdStrategy() == DaoConfig.IdStrategyEnum.UUID) { theResource.setId(UUID.randomUUID().toString()); + theResource.setUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED, Boolean.TRUE); } - return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource); + return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails, requestPartitionId); } @Override @@ -181,6 +237,7 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) { validateIdPresentForDelete(theId); + validateDeleteEnabled(); final ResourceTable entity = readEntityLatestVersion(theId, theRequest); if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) { @@ -258,6 +315,7 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) { validateIdPresentForDelete(theId); + validateDeleteEnabled(); DeleteConflictList deleteConflicts = new DeleteConflictList(); if (isNotBlank(theId.getValue())) { @@ -280,6 +338,8 @@ public abstract class BaseHapiFhirResourceDao extends B */ @Override public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) { + validateDeleteEnabled(); + StopWatch w = new StopWatch(); Set resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest); @@ -355,6 +415,8 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) { + validateDeleteEnabled(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); @@ -364,6 +426,13 @@ public abstract class BaseHapiFhirResourceDao extends B return outcome; } + private void validateDeleteEnabled() { + if (!myDaoConfig.isDeleteEnabled()) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "deleteBlockedBecauseDisabled"); + throw new PreconditionFailedException(msg); + } + } + private void validateIdPresentForDelete(IIdType theId) { if (theId == null || !theId.hasIdPart()) { throw new InvalidRequestException("Can not perform delete, no ID provided"); @@ -377,13 +446,14 @@ public abstract class BaseHapiFhirResourceDao extends B } } - private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest) { + private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { StopWatch w = new StopWatch(); preProcessResourceForStorage(theResource); ResourceTable entity = new ResourceTable(); entity.setResourceType(toResourceName(theResource)); + entity.setPartitionId(theRequestPartitionId); if (isNotBlank(theIfNoneExist)) { Set match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest); @@ -401,22 +471,27 @@ public abstract class BaseHapiFhirResourceDao extends B boolean serverAssignedId; if (isNotBlank(theResource.getIdElement().getIdPart())) { - switch (myDaoConfig.getResourceClientIdStrategy()) { - case NOT_ALLOWED: - throw new ResourceNotFoundException( - getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart())); - case ALPHANUMERIC: - if (theResource.getIdElement().isIdPartValidLong()) { - throw new InvalidRequestException( - getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart())); - } - createForcedIdIfNeeded(entity, theResource.getIdElement(), false); - break; - case ANY: - createForcedIdIfNeeded(entity, theResource.getIdElement(), true); - break; + if (theResource.getUserData(JpaConstants.RESOURCE_ID_SERVER_ASSIGNED) == Boolean.TRUE) { + createForcedIdIfNeeded(entity, theResource.getIdElement(), true); + serverAssignedId = true; + } else { + switch (myDaoConfig.getResourceClientIdStrategy()) { + case NOT_ALLOWED: + throw new ResourceNotFoundException( + getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart())); + case ALPHANUMERIC: + if (theResource.getIdElement().isIdPartValidLong()) { + throw new InvalidRequestException( + getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart())); + } + createForcedIdIfNeeded(entity, theResource.getIdElement(), false); + break; + case ANY: + createForcedIdIfNeeded(entity, theResource.getIdElement(), true); + break; + } + serverAssignedId = false; } - serverAssignedId = false; } else { serverAssignedId = true; } @@ -427,7 +502,7 @@ public abstract class BaseHapiFhirResourceDao extends B notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails); } - // Notify JPA interceptors + // Interceptor call: STORAGE_PRESTORAGE_RESOURCE_CREATED HookParams hookParams = new HookParams() .add(IBaseResource.class, theResource) .add(RequestDetails.class, theRequest) @@ -610,6 +685,11 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) { + if (myPartitionSettings.isPartitioningEnabled()) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "noSystemOrTypeHistoryForPartitionAwareServer"); + throw new MethodNotAllowedException(msg); + } + // Notify interceptors ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails); notifyInterceptors(RestOperationTypeEnum.HISTORY_TYPE, requestDetails); @@ -918,7 +998,6 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) { - return readEntity(theId, true, theRequest); } @@ -926,9 +1005,28 @@ public abstract class BaseHapiFhirResourceDao extends B public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) { validateResourceTypeAndThrowInvalidRequestException(theId); - ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); + @Nullable RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName()); + ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, getResourceName(), theId.getIdPart()); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong()); + // Verify that the resource is for the correct partition + if (requestPartitionId != null) { + if (requestPartitionId.getPartitionId() == null) { + if (entity.getPartitionId() != null) { + ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId()); + entity = null; + } + } else if (entity.getPartitionId() != null) { + if (!entity.getPartitionId().getPartitionId().equals(requestPartitionId.getPartitionId())) { + ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId()); + entity = null; + } + } else { + ourLog.debug("Performing a read for PartitionId=null but entity has partition: {}", entity.getPartitionId()); + entity = null; + } + } + if (entity == null) { throw new ResourceNotFoundException(theId); } @@ -964,8 +1062,17 @@ public abstract class BaseHapiFhirResourceDao extends B return entity; } - protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequest) { - ResourcePersistentId persistentId = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); + @NotNull + protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails) { + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName()); + return readEntityLatestVersion(theId, requestPartitionId); + } + + @NotNull + private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable RequestPartitionId theRequestPartitionId) { + validateResourceTypeAndThrowInvalidRequestException(theId); + + ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, getResourceName(), theId.getIdPart()); ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId()); if (entity == null) { throw new ResourceNotFoundException(theId); @@ -1101,7 +1208,9 @@ public abstract class BaseHapiFhirResourceDao extends B String uuid = UUID.randomUUID().toString(); SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); - try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest)) { + + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName()); + try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) { while (iter.hasNext()) { retVal.add(iter.next()); } @@ -1205,10 +1314,11 @@ public abstract class BaseHapiFhirResourceDao extends B */ resourceId = theResource.getIdElement(); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource); try { - entity = readEntityLatestVersion(resourceId, theRequest); + entity = readEntityLatestVersion(resourceId, requestPartitionId); } catch (ResourceNotFoundException e) { - return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest); + return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest, requestPartitionId); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index c1c16f510fc..7d179146267 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -1,11 +1,14 @@ package ca.uhn.fhir.jpa.dao; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.StopWatch; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -46,6 +49,8 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao extends BaseHapiFhirDao paramNames = theSource.keySet(); for (String nextParamName : paramNames) { - QualifierDetails qualifiedParamName = SearchMethodBinding.extractQualifiersFromParameterName(nextParamName); + QualifierDetails qualifiedParamName = QualifierDetails.extractQualifiersFromParameterName(nextParamName); RuntimeSearchParam param = searchParams.get(qualifiedParamName.getParamName()); if (param == null) { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet<>(searchParams.keySet())); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java index 1fa898bf1c0..bb8bc4096e1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -25,14 +25,18 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; -import ca.uhn.fhir.jpa.delete.DeleteConflictOutcome; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IJpaDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.parser.DataFormatException; @@ -667,7 +671,7 @@ public abstract class BaseTransactionProcessor { // DELETE String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); - ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); + IFhirResourceDao dao = toDao(parts, verb, url); int status = Constants.STATUS_HTTP_204_NO_CONTENT; if (parts.getResourceId() != null) { IIdType deleteId = newIdType(parts.getResourceType(), parts.getResourceId()); @@ -770,7 +774,7 @@ public abstract class BaseTransactionProcessor { throw new InvalidRequestException(msg); } - ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); + IFhirResourceDao dao = toDao(parts, verb, url); PatchTypeEnum patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType); IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId()); DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, theRequest); @@ -986,7 +990,7 @@ public abstract class BaseTransactionProcessor { return url; } - private ca.uhn.fhir.jpa.dao.IFhirResourceDao toDao(UrlUtil.UrlParts theParts, String theVerb, String theUrl) { + private IFhirResourceDao toDao(UrlUtil.UrlParts theParts, String theVerb, String theUrl) { RuntimeResourceDefinition resType; try { resType = myContext.getResourceDefinition(theParts.getResourceType()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java index dcb3d06dbae..07bb9b36c4c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java @@ -20,21 +20,20 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.support.TransactionTemplate; @Service +@Primary public class DaoSearchParamProvider implements ISearchParamProvider { - @Autowired - private PlatformTransactionManager myTxManager; + @Autowired private DaoRegistry myDaoRegistry; @@ -45,7 +44,6 @@ public class DaoSearchParamProvider implements ISearchParamProvider { @Override public int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval) { - TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); - return txTemplate.execute(t-> theSearchParamRegistry.doRefresh(theRefreshInterval)); + return theSearchParamRegistry.doRefresh(theRefreshInterval); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java index 49786660706..d4a0461fe62 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; import ca.uhn.fhir.model.dstu2.resource.Composition; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -28,7 +29,6 @@ import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoEncounterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoEncounterDstu2.java index b3513d8dcf7..a4fd3d11be4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoEncounterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoEncounterDstu2.java @@ -20,14 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; - +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoEncounter; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Encounter; @@ -35,6 +29,11 @@ import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.StringParam; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; public class FhirResourceDaoEncounterDstu2 extends BaseHapiFhirResourceDaoimplements IFhirResourceDaoEncounter { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java index 21a607766c4..750911b9d42 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoMessageHeader; import ca.uhn.fhir.model.dstu2.resource.MessageHeader; -import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import org.hl7.fhir.instance.model.api.IBaseBundle; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java index f9f5ac115f7..f479846db4d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java @@ -20,22 +20,26 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; - +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; public class FhirResourceDaoPatientDstu2 extends BaseHapiFhirResourceDaoimplements IFhirResourceDaoPatient { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java index da4cacaddd1..4672d60f996 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java @@ -21,11 +21,11 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.model.dstu2.composite.MetaDt; -import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.SearchParameter; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum; @@ -38,8 +38,6 @@ import java.util.List; public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - @Autowired - private IFhirSystemDao mySystemDao; @Autowired private ISearchParamExtractor mySearchParamExtractor; @@ -79,8 +77,9 @@ public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao String expression = theResource.getXpath(); FhirContext context = getContext(); SearchParamTypeEnum type = theResource.getTypeElement().getValueAsEnum(); + String code = theResource.getCode(); - FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); + FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoStructureDefinitionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoStructureDefinitionDstu2.java index bf0f1e42476..90012bf1818 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoStructureDefinitionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoStructureDefinitionDstu2.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.model.dstu2.resource.StructureDefinition; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java index adc771ab18a..ad9a8c630a3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -20,16 +20,13 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.model.dstu2.resource.Subscription; -import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; -import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; @@ -37,8 +34,6 @@ import org.springframework.transaction.PlatformTransactionManager; import java.util.Date; -import static org.apache.commons.lang3.StringUtils.isBlank; - public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSubscription { @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java index 9e94695a516..de091e9dbdf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java @@ -21,7 +21,10 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -41,9 +44,8 @@ import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.apache.commons.codec.binary.StringUtils; -import org.hl7.fhir.instance.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; -import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; @@ -64,7 +66,7 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao valueSetIds; Set ids = searchForIds(new SearchParameterMap(ValueSet.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); - valueSetIds = new ArrayList(); + valueSetIds = new ArrayList<>(); for (ResourcePersistentId next : ids) { IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "ValueSet", next); valueSetIds.add(id); @@ -194,7 +196,7 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theContains, String theSystem, String theCode) { + private IValidationSupport.LookupCodeResult lookup(List theContains, String theSystem, String theCode) { for (ExpansionContains nextCode : theContains) { String system = nextCode.getSystem(); String code = nextCode.getCode(); if (theSystem.equals(system) && theCode.equals(code)) { - IContextValidationSupport.LookupCodeResult retVal = new IContextValidationSupport.LookupCodeResult(); + IValidationSupport.LookupCodeResult retVal = new IValidationSupport.LookupCodeResult(); retVal.setSearchedForCode(code); retVal.setSearchedForSystem(system); retVal.setFound(true); @@ -232,7 +234,7 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, CodingDt theCoding, RequestDetails theRequest) { + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, CodingDt theCoding, RequestDetails theRequest) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -258,13 +260,13 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao contains = expansion.getExpansion().getContains(); - IContextValidationSupport.LookupCodeResult result = lookup(contains, system, code); + IValidationSupport.LookupCodeResult result = lookup(contains, system, code); if (result != null) { return result; } } - IContextValidationSupport.LookupCodeResult retVal = new IContextValidationSupport.LookupCodeResult(); + IValidationSupport.LookupCodeResult retVal = new IValidationSupport.LookupCodeResult(); retVal.setFound(false); retVal.setSearchedForCode(code); retVal.setSearchedForSystem(system); @@ -280,7 +282,7 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, + public ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequest) { List valueSetIds; @@ -345,10 +347,10 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao contains, String theSystem, String theCode, CodingDt theCoding, + private ValidateCodeResult validateCodeIsInContains(List contains, String theSystem, String theCode, CodingDt theCoding, CodeableConceptDt theCodeableConcept) { for (ExpansionContains nextCode : contains) { - ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept); + ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept); if (result != null) { return result; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 3eae1ef90cb..44ff9fc78ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -21,7 +21,11 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; @@ -78,7 +82,9 @@ import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.TypedQuery; import java.util.*; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class); @@ -572,7 +578,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { return retVal; } - private ca.uhn.fhir.jpa.dao.IFhirResourceDao toDao(UrlParts theParts, String theVerb, String theUrl) { + private IFhirResourceDao toDao(UrlParts theParts, String theVerb, String theUrl) { RuntimeResourceDefinition resType; try { resType = getContext().getResourceDefinition(theParts.getResourceType()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index bdeb9ab76a3..1783e06a261 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -20,10 +20,14 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.Constants; @@ -39,7 +43,10 @@ import org.apache.commons.lang3.Validate; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.search.Query; import org.apache.lucene.search.highlight.Formatter; -import org.apache.lucene.search.highlight.*; +import org.apache.lucene.search.highlight.Highlighter; +import org.apache.lucene.search.highlight.QueryScorer; +import org.apache.lucene.search.highlight.Scorer; +import org.apache.lucene.search.highlight.TokenGroup; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.query.dsl.BooleanJunction; @@ -53,7 +60,12 @@ import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -229,7 +241,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { StringParam idParm = (StringParam) idParam; idParamValue = idParm.getValue(); } - pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue, theRequest); +// pid = myIdHelperService.translateForcedIdToPid_(theResourceName, idParamValue, theRequest); } ResourcePersistentId referencingPid = pid; @@ -269,6 +281,12 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { return doSearch(theResourceName, theParams, null); } + @Autowired + private IRequestPartitionHelperService myRequestPartitionHelperService; + + @Autowired + private PartitionSettings myPartitionSettings; + @Transactional() @Override public List suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest) { @@ -282,7 +300,12 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) { throw new InvalidRequestException("Invalid context: " + theContext); } - ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1], theRequest); + + // Partitioning is not supported for this operation + Validate.isTrue(myPartitionSettings.isPartitioningEnabled() == false, "Suggest keywords not supported for partitioned system"); + RequestPartitionId requestPartitionId = null; + + ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, contextParts[0], contextParts[1]); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -298,7 +321,6 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { .sentence(theText.toLowerCase()).createQuery(); Query query = qb.bool() -// .must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery()) .must(qb.keyword().onField("myResourceLinksField").matching(pid.toString()).createQuery()) .must(textQuery) .createQuery(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaValidationSupportDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IHapiJpaRepository.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaValidationSupportDstu2.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IHapiJpaRepository.java index 45008fd8cd7..4ac0d295d41 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaValidationSupportDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IHapiJpaRepository.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao; -/* +/*- * #%L * HAPI FHIR JPA Server * %% @@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +import org.springframework.data.jpa.repository.JpaRepository; -public interface IJpaValidationSupportDstu2 extends IValidationSupport { +public interface IHapiJpaRepository extends JpaRepository { + + void deleteByPid(Long theId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java index b4752748e78..090e47a7a1e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -37,16 +38,16 @@ import java.util.Set; public interface ISearchBuilder { - IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest); + IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest, RequestPartitionId theRequestPartitionId); + + Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId); void setMaxResultsToFetch(Integer theMaxResultsToFetch); - Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest); - void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails); Set loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, - DateRangeParam theLastUpdated, String theSearchIdOrDescription, RequestDetails theRequest); + DateRangeParam theLastUpdated, String theSearchIdOrDescription, RequestDetails theRequest); /** * How many results may be fetched at once diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java new file mode 100644 index 00000000000..bd8a11fee29 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java @@ -0,0 +1,195 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.UriParam; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ImplementationGuide; +import org.hl7.fhir.r4.model.Questionnaire; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; +import javax.transaction.Transactional; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * This class is a {@link IValidationSupport Validation support} module that loads + * validation resources (StructureDefinition, ValueSet, CodeSystem, etc.) from the resources + * persisted in the JPA server. + */ +@Transactional(value = Transactional.TxType.REQUIRED) +public class JpaPersistedResourceValidationSupport implements IValidationSupport { + + private static final Logger ourLog = LoggerFactory.getLogger(JpaPersistedResourceValidationSupport.class); + + private final FhirContext myFhirContext; + + @Autowired + private DaoRegistry myDaoRegistry; + private Class myCodeSystemType; + private Class myStructureDefinitionType; + private Class myValueSetType; + private Class myQuestionnaireType; + private Class myImplementationGuideType; + + /** + * Constructor + */ + public JpaPersistedResourceValidationSupport(FhirContext theFhirContext) { + super(); + Validate.notNull(theFhirContext); + myFhirContext = theFhirContext; + } + + + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + return fetchResource(myCodeSystemType, theSystem); + } + + @Override + public IBaseResource fetchValueSet(String theSystem) { + return fetchResource(myValueSetType, theSystem); + } + + @Override + public IBaseResource fetchStructureDefinition(String theUrl) { + return fetchResource(myStructureDefinitionType, theUrl); + } + + + @Override + @SuppressWarnings({"unchecked", "unused"}) + public T fetchResource(Class theClass, String theUri) { + if (isBlank(theUri)) { + return null; + } + + IdType id = new IdType(theUri); + boolean localReference = false; + if (id.hasBaseUrl() == false && id.hasIdPart() == true) { + localReference = true; + } + + String resourceName = myFhirContext.getResourceDefinition(theClass).getName(); + IBundleProvider search; + if ("ValueSet".equals(resourceName)) { + if (localReference) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(IAnyResource.SP_RES_ID, new StringParam(theUri)); + search = myDaoRegistry.getResourceDao("ValueSet").search(params); + if (search.size() == 0) { + params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(ValueSet.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("ValueSet").search(params); + } + } else { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(ValueSet.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("ValueSet").search(params); + } + } else if ("StructureDefinition".equals(resourceName)) { + // Don't allow the core FHIR definitions to be overwritten + if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { + String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); + if (myFhirContext.getElementDefinition(typeName) != null) { + return null; + } + } + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(StructureDefinition.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("StructureDefinition").search(params); + } else if ("Questionnaire".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) { + params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart())); + } else { + params.add(Questionnaire.SP_URL, new UriParam(id.getValue())); + } + search = myDaoRegistry.getResourceDao("Questionnaire").search(params); + } else if ("CodeSystem".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(CodeSystem.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao(resourceName).search(params); + } else if ("ImplementationGuide".equals(resourceName)) { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(1); + params.add(ImplementationGuide.SP_URL, new UriParam(theUri)); + search = myDaoRegistry.getResourceDao("ImplementationGuide").search(params); + } else { + throw new IllegalArgumentException("Can't fetch resource type: " + resourceName); + } + + Integer size = search.size(); + if (size == null || size == 0) { + return null; + } + + if (size > 1) { + ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri); + } + + return (T) search.getResources(0, 1).get(0); + } + + @Override + public FhirContext getFhirContext() { + return myFhirContext; + } + + @PostConstruct + public void start() { + myStructureDefinitionType = myFhirContext.getResourceDefinition("StructureDefinition").getImplementingClass(); + myValueSetType = myFhirContext.getResourceDefinition("ValueSet").getImplementingClass(); + myQuestionnaireType = myFhirContext.getResourceDefinition("Questionnaire").getImplementingClass(); + myImplementationGuideType = myFhirContext.getResourceDefinition("ImplementationGuide").getImplementingClass(); + + if (myFhirContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) { + myCodeSystemType = myFhirContext.getResourceDefinition("CodeSystem").getImplementingClass(); + } else { + myCodeSystemType = myFhirContext.getResourceDefinition("ValueSet").getImplementingClass(); + } + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaValidationSupportDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaValidationSupportDstu2.java deleted file mode 100644 index 126f7c73d1f..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaValidationSupportDstu2.java +++ /dev/null @@ -1,144 +0,0 @@ -package ca.uhn.fhir.jpa.dao; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.dstu2.resource.Questionnaire; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.UriParam; -import org.hl7.fhir.dstu2.model.IdType; -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; -import java.util.ArrayList; -import java.util.List; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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% - */ - -@Transactional(value = TxType.REQUIRED) -public class JpaValidationSupportDstu2 implements IJpaValidationSupportDstu2 { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaValidationSupportDstu2.class); - - @Autowired - @Qualifier("myFhirContextDstu2Hl7Org") - private FhirContext myRiCtx; - - @Autowired - @Qualifier("myStructureDefinitionDaoDstu2") - private IFhirResourceDao myStructureDefinitionDao; - - @Autowired - @Qualifier("myQuestionnaireDaoDstu2") - private IFhirResourceDao myQuestionnaireDao; - - @Autowired - @Qualifier("myValueSetDaoDstu2") - private IFhirResourceDao myValueSetDao; - - @Autowired - @Qualifier("myFhirContextDstu2") - private FhirContext myDstu2Ctx; - - @Override - public List allStructures() { - return new ArrayList<>(); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - return null; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public ValueSet fetchCodeSystem(FhirContext theCtx, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - String resourceName = myRiCtx.getResourceDefinition(theClass).getName(); - IBundleProvider search; - IdType uriAsId = new IdType(theUri); - if ("ValueSet".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.add(ca.uhn.fhir.model.dstu2.resource.ValueSet.SP_URL, new UriParam(theUri)); - params.setLoadSynchronousUpTo(10); - search = myValueSetDao.search(params); - } else if ("StructureDefinition".equals(resourceName)) { - search = myStructureDefinitionDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ca.uhn.fhir.model.dstu2.resource.StructureDefinition.SP_URL, new UriParam(theUri))); - } else if ("Questionnaire".equals(resourceName)) { - search = myQuestionnaireDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Questionnaire.SP_RES_ID, new TokenParam(null, theUri))); - } else { - throw new IllegalArgumentException("Can't fetch resource type: " + resourceName); - } - - if (search.size() == 0) { - if ("ValueSet".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.add(ca.uhn.fhir.model.dstu2.resource.ValueSet.SP_RES_ID, new TokenParam(null, uriAsId.toUnqualifiedVersionless().getValue())); - params.setLoadSynchronousUpTo(10); - search = myValueSetDao.search(params); - if (search.size() == 0) { - return null; - } - } else { - return null; - } - } - - if (search.size() > 1) { - ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri); - } - - IBaseResource res = search.getResources(0, 1).get(0); - - /* - * Validator wants RI structures and not HAPI ones, so convert - * - * TODO: we really need a more efficient way of converting.. Or maybe this will just go away when we move to RI structures - */ - String encoded = myDstu2Ctx.newJsonParser().encodeResourceToString(res); - return myRiCtx.newJsonParser().parseResource(theClass, encoded); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - return false; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { - return null; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java index 2d545f14884..703e9412109 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java @@ -25,6 +25,8 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; @@ -81,7 +83,6 @@ public class MatchResourceUrlService { .add(StorageProcessingMessage.class, message); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); } - return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 5a21d9a7933..ea93205d9ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -21,11 +21,15 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; @@ -36,6 +40,7 @@ import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum; import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey; import ca.uhn.fhir.jpa.entity.ResourceSearchView; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; @@ -47,6 +52,7 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper; import ca.uhn.fhir.jpa.util.BaseIterator; import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; @@ -77,7 +83,6 @@ import org.apache.commons.lang3.Validate; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.query.Query; -import org.hl7.fhir.dstu3.model.Location; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; @@ -87,7 +92,6 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; @@ -131,10 +135,11 @@ public class SearchBuilder implements ISearchBuilder { private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class); - private static ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L); + private static final ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L); private final QueryRoot myQueryRoot = new QueryRoot(); private final String myResourceName; private final Class myResourceType; + private final IDao myCallingDao; @Autowired protected IInterceptorBroadcaster myInterceptorBroadcaster; @Autowired @@ -156,14 +161,16 @@ public class SearchBuilder implements ISearchBuilder { @Autowired private PredicateBuilderFactory myPredicateBuilderFactory; private List myAlsoIncludePids; - private CriteriaBuilder myBuilder; - private IDao myCallingDao; + private CriteriaBuilder myCriteriaBuilder; private SearchParameterMap myParams; private String mySearchUuid; private int myFetchSize; private Integer myMaxResultsToFetch; private Set myPidSet; private PredicateBuilder myPredicateBuilder; + private RequestPartitionId myRequestPartitionId; + @Autowired + private PartitionSettings myPartitionSettings; /** * Constructor @@ -180,7 +187,7 @@ public class SearchBuilder implements ISearchBuilder { } private void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest) { - myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest); + myPredicateBuilder.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, myRequestPartitionId); } private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams, RequestDetails theRequest) { @@ -189,19 +196,13 @@ public class SearchBuilder implements ISearchBuilder { // Remove any empty parameters theParams.clean(); - if (myResourceType == Location.class) { - theParams.setLocationDistance(); + // For DSTU3, pull out near-distance first so when it comes time to evaluate near, we already know the distance + if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) { + Dstu3DistanceHelper.setNearDistance(myResourceType, theParams); } - /* - * Check if there is a unique key associated with the set - * of parameters passed in - */ - boolean couldBeEligibleForCompositeUniqueSpProcessing = - myDaoConfig.isUniqueIndexesEnabled() && - myParams.getEverythingMode() == null && - myParams.isAllParametersHaveNoModifier(); - if (couldBeEligibleForCompositeUniqueSpProcessing) { + // Attempt to lookup via composite unique key. + if (isCompositeUniqueSpCandidate()) { attemptCompositeUniqueSpProcessing(theParams, theRequest); } @@ -211,12 +212,21 @@ public class SearchBuilder implements ISearchBuilder { List> andOrParams = nextParamEntry.getValue(); searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams, theRequest); } + } + /** + * A search is a candidate for Composite Unique SP if unique indexes are enabled, there is no EverythingMode, and the + * parameters all have no modifiers. + */ + private boolean isCompositeUniqueSpCandidate() { + return myDaoConfig.isUniqueIndexesEnabled() && + myParams.getEverythingMode() == null && + myParams.isAllParametersHaveNoModifier(); } @Override - public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest) { - init(theParams, theSearchUuid); + public Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + init(theParams, theSearchUuid, theRequestPartitionId); TypedQuery query = createQuery(null, null, true, theRequest); return new CountQueryIterator(query); @@ -226,13 +236,13 @@ public class SearchBuilder implements ISearchBuilder { * @param thePidSet May be null */ @Override - public void setPreviouslyAddedResourcePids(@Nullable List thePidSet) { + public void setPreviouslyAddedResourcePids(@Nonnull List thePidSet) { myPidSet = new HashSet<>(thePidSet); } @Override - public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) { - init(theParams, theSearchRuntimeDetails.getSearchUuid()); + public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId); if (myPidSet == null) { myPidSet = new HashSet<>(); @@ -241,13 +251,15 @@ public class SearchBuilder implements ISearchBuilder { return new QueryIterator(theSearchRuntimeDetails, theRequest); } - private void init(SearchParameterMap theParams, String theTheSearchUuid) { + private void init(SearchParameterMap theParams, String theSearchUuid, RequestPartitionId theRequestPartitionId) { myParams = theParams; - myBuilder = myEntityManager.getCriteriaBuilder(); - mySearchUuid = theTheSearchUuid; + myCriteriaBuilder = myEntityManager.getCriteriaBuilder(); + mySearchUuid = theSearchUuid; myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory); + myRequestPartitionId = theRequestPartitionId; } + private TypedQuery createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) { CriteriaQuery outerQuery; /* @@ -259,50 +271,49 @@ public class SearchBuilder implements ISearchBuilder { if (sort != null) { assert !theCount; - outerQuery = myBuilder.createQuery(Long.class); + outerQuery = myCriteriaBuilder.createQuery(Long.class); myQueryRoot.push(outerQuery); if (theCount) { - outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot())); + outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot())); } else { outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class)); } List orders = Lists.newArrayList(); - createSort(myBuilder, myQueryRoot, sort, orders); + createSort(myCriteriaBuilder, myQueryRoot, sort, orders); if (orders.size() > 0) { outerQuery.orderBy(orders); } } else { - outerQuery = myBuilder.createQuery(Long.class); + outerQuery = myCriteriaBuilder.createQuery(Long.class); myQueryRoot.push(outerQuery); if (theCount) { - outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot())); + outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot())); } else { outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class)); // KHS This distinct call is causing performance issues in large installations // outerQuery.distinct(true); } - } if (myParams.getEverythingMode() != null) { Join join = myQueryRoot.join("myResourceLinks", JoinType.LEFT); if (myParams.get(IAnyResource.SP_RES_ID) != null) { - StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); - ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue(), theRequest); + StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); + ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, idParam.getValue()); if (myAlsoIncludePids == null) { myAlsoIncludePids = new ArrayList<>(1); } myAlsoIncludePids.add(pid); - myQueryRoot.addPredicate(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong())); + myQueryRoot.addPredicate(myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong())); } else { - Predicate targetTypePredicate = myBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName); - Predicate sourceTypePredicate = myBuilder.equal(myQueryRoot.get("myResourceType").as(String.class), myResourceName); - myQueryRoot.addPredicate(myBuilder.or(sourceTypePredicate, targetTypePredicate)); + Predicate targetTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName); + Predicate sourceTypePredicate = myCriteriaBuilder.equal(myQueryRoot.get("myResourceType").as(String.class), myResourceName); + myQueryRoot.addPredicate(myCriteriaBuilder.or(sourceTypePredicate, targetTypePredicate)); } } else { @@ -345,17 +356,24 @@ public class SearchBuilder implements ISearchBuilder { */ if (!myQueryRoot.hasIndexJoins()) { if (myParams.getEverythingMode() == null) { - myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName)); + myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName)); + } + myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted"))); + if (myRequestPartitionId != null) { + if (myRequestPartitionId.getPartitionId() != null) { + myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId())); + } else { + myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myPartitionIdValue").as(Integer.class))); + } } - myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted"))); } // Last updated DateRangeParam lu = myParams.getLastUpdated(); - List lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myQueryRoot.getRoot()); + List lastUpdatedPredicates = createLastUpdatedPredicates(lu, myCriteriaBuilder, myQueryRoot.getRoot()); myQueryRoot.addPredicates(lastUpdatedPredicates); - myQueryRoot.where(myBuilder.and(myQueryRoot.getPredicateArray())); + myQueryRoot.where(myCriteriaBuilder.and(myQueryRoot.getPredicateArray())); /* * Now perform the search @@ -470,7 +488,7 @@ public class SearchBuilder implements ISearchBuilder { Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName()); theQueryRoot.addPredicate(joinParam1); } else { - Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName()); + Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionSettings, myRequestPartitionId, myResourceName, theSort.getParamName()); Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity); theQueryRoot.addPredicate(joinParam1); } @@ -504,6 +522,9 @@ public class SearchBuilder implements ISearchBuilder { ResourcePersistentId resourceId; for (ResourceSearchView next : resourceSearchViewList) { + if (next.getDeleted() != null) { + continue; + } Class resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass(); @@ -856,7 +877,7 @@ public class SearchBuilder implements ISearchBuilder { .add(StorageProcessingMessage.class, msg); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); - addPredicateCompositeStringUnique(theParams, indexString); + addPredicateCompositeStringUnique(theParams, indexString, myRequestPartitionId); } } } @@ -871,10 +892,17 @@ public class SearchBuilder implements ISearchBuilder { } } - private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString) { - myQueryRoot.setHasIndexJoins(true); + private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) { Join join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT); - Predicate predicate = myBuilder.equal(join.get("myIndexString"), theIndexedString); + + if (theRequestPartitionId != null) { + Integer partitionId = theRequestPartitionId.getPartitionId(); + Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId); + myQueryRoot.addPredicate(predicate); + } + + myQueryRoot.setHasIndexJoins(); + Predicate predicate = myCriteriaBuilder.equal(join.get("myIndexString"), theIndexedString); myQueryRoot.addPredicate(predicate); // Remove any empty parameters remaining after this @@ -901,7 +929,7 @@ public class SearchBuilder implements ISearchBuilder { } public CriteriaBuilder getBuilder() { - return myBuilder; + return myCriteriaBuilder; } public QueryRoot getQueryRoot() { @@ -916,10 +944,6 @@ public class SearchBuilder implements ISearchBuilder { return myResourceName; } - public IDao getCallingDao() { - return myCallingDao; - } - @VisibleForTesting public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) { myDaoConfig = theDaoConfig; @@ -978,7 +1002,7 @@ public class SearchBuilder implements ISearchBuilder { private final SearchRuntimeDetails mySearchRuntimeDetails; private final RequestDetails myRequest; private final boolean myHaveRawSqlHooks; - private final boolean myHavePerftraceFoundIdHook; + private final boolean myHavePerfTraceFoundIdHook; private boolean myFirst = true; private IncludesIterator myIncludesIterator; private ResourcePersistentId myNext; @@ -999,7 +1023,7 @@ public class SearchBuilder implements ISearchBuilder { myStillNeedToFetchIncludes = true; } - myHavePerftraceFoundIdHook = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest); + myHavePerfTraceFoundIdHook = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest); myHaveRawSqlHooks = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, myRequest); } @@ -1041,7 +1065,7 @@ public class SearchBuilder implements ISearchBuilder { if (myNext == null) { while (myResultsIterator.hasNext()) { Long nextLong = myResultsIterator.next(); - if (myHavePerftraceFoundIdHook) { + if (myHavePerfTraceFoundIdHook) { HookParams params = new HookParams() .add(Integer.class, System.identityHashCode(this)) .add(Object.class, nextLong); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java index ddce293a4a8..820e45e7875 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IDao; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Lookup; import org.springframework.stereotype.Service; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TolerantJsonParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TolerantJsonParser.java new file mode 100644 index 00000000000..b8a9d50ff80 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TolerantJsonParser.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParserErrorHandler; +import ca.uhn.fhir.parser.JsonParser; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.Iterator; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.defaultString; + +class TolerantJsonParser extends JsonParser { + + TolerantJsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { + super(theContext, theParserErrorHandler); + } + + @Override + public T parseResource(Class theResourceType, String theMessageString) { + try { + return super.parseResource(theResourceType, theMessageString); + } catch (DataFormatException e) { + + /* + * The following is a hacky and gross workaround until the following PR is hopefully merged: + * https://github.com/FasterXML/jackson-core/pull/611 + * + * The issue this solves is that under Gson it was possible to store JSON containing + * decimal numbers with no leading integer (e.g. .123) and numbers with double leading + * zeros (e.g. 000.123). + * + * These don't parse in Jackson (which is valid behaviour, these aren't ok according to the + * JSON spec), meaning we can be stuck with data in the database that can't be loaded back out. + * + * Note that if we fix this in the future to rely on Jackson natively handing this + * nicely we may or may not be able to remove some code from + * ParserState.Primitive state too. + */ + + String msg = defaultString(e.getMessage()); + if (msg.contains("Unexpected character ('.' (code 46))") || msg.contains("Invalid numeric value: Leading zeroes not allowed")) { + Gson gson = new Gson(); + + JsonObject object = gson.fromJson(theMessageString, JsonObject.class); + String corrected = gson.toJson(object); + + return super.parseResource(theResourceType, corrected); + } + + throw e; + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java index 0b694aae773..f67e85a36f9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect; import ca.uhn.fhir.util.StopWatch; import org.apache.commons.lang3.Validate; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java index 65738c08c77..1a8d31c684e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.data; import java.util.Collection; import java.util.List; +import java.util.Optional; /* * #%L @@ -35,8 +36,14 @@ public interface IForcedIdDao extends JpaRepository { @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)") List findByForcedId(@Param("forced_id") Collection theForcedId); - @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId IN (:forced_id)") - List findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedId); + @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id") + Optional findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId); + + @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId IS NULL AND myResourceType = :resource_type AND myForcedId = :forced_id") + Optional findByPartitionIdNullAndTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId); + + @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myPartitionId.myPartitionId = :partition_id AND myResourceType = :resource_type AND myForcedId = :forced_id") + Optional findByPartitionIdAndTypeAndForcedId(@Param("partition_id") Integer thePartitionId, @Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId); @Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid") ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid); @@ -44,4 +51,78 @@ public interface IForcedIdDao extends JpaRepository { @Modifying @Query("DELETE FROM ForcedId t WHERE t.myId = :pid") void deleteByPid(@Param("pid") Long theId); + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId IN ( :forced_id )") + Collection findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedId); + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue = :partition_id AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )") + Collection findByTypeAndForcedIdInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedId, @Param("partition_id") Integer thePartitionId); + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myPartitionIdValue IS NULL AND myResourceType = :resource_type AND myForcedId IN ( :forced_id )") + Collection findByTypeAndForcedIdInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedId); + + /** + * Warning: No DB index exists for this particular query, so it may not perform well + * + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("" + + "SELECT " + + " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " + + "FROM ForcedId f " + + "JOIN ResourceTable t ON t.myId = f.myResourcePid " + + "WHERE f.myForcedId IN ( :forced_id )") + Collection findAndResolveByForcedIdWithNoType(@Param("forced_id") Collection theForcedIds); + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("" + + "SELECT " + + " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " + + "FROM ForcedId f " + + "JOIN ResourceTable t ON t.myId = f.myResourcePid " + + "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )") + Collection findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedIds); + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("" + + "SELECT " + + " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " + + "FROM ForcedId f " + + "JOIN ResourceTable t ON t.myId = f.myResourcePid " + + "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue = :partition_id") + Collection findAndResolveByForcedIdWithNoTypeInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedIds, @Param("partition_id") Integer thePartitionId); + + + /** + * This method returns a Collection where each row is an element in the collection. Each element in the collection + * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. + */ + @Query("" + + "SELECT " + + " f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " + + "FROM ForcedId f " + + "JOIN ResourceTable t ON t.myId = f.myResourcePid " + + "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IS NULL") + Collection findAndResolveByForcedIdWithNoTypeInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection theForcedIds); + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/IJpaValidationSupportR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IPartitionDao.java similarity index 51% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/IJpaValidationSupportR5.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IPartitionDao.java index d46d8cc227b..d8df1c5fbeb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/IJpaValidationSupportR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IPartitionDao.java @@ -1,6 +1,12 @@ -package ca.uhn.fhir.jpa.dao.r5; +package ca.uhn.fhir.jpa.dao.data; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import org.checkerframework.checker.nullness.Opt; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; /* * #%L @@ -12,7 +18,7 @@ import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; * 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 + * 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, @@ -22,6 +28,9 @@ import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; * #L% */ -public interface IJpaValidationSupportR5 extends IValidationSupport { - // nothing yet +public interface IPartitionDao extends JpaRepository { + + @Query("SELECT p FROM PartitionEntity p WHERE p.myName = :name") + Optional findForName(@Param("name") String theName); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java index c1c2bf8d069..1e142e91ca1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedCompositeStringUniqueDao.java @@ -25,15 +25,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Collection; -import java.util.Optional; +import java.util.List; public interface IResourceIndexedCompositeStringUniqueDao extends JpaRepository { @Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString = :str") ResourceIndexedCompositeStringUnique findByQueryString(@Param("str") String theQueryString); - @Query("SELECT r.myResourceId FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString IN :str") - Collection findResourcePidsByQueryStrings(@Param("str") Collection theQueryString); - + @Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myResourceId = :resId") + List findAllForResourceIdForUnitTest(@Param("resId") Long theResourceId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java index c37f62e6df7..457cd6515fb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamDateDao.java @@ -20,15 +20,19 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import org.springframework.data.jpa.repository.JpaRepository; - import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; + public interface IResourceIndexedSearchParamDateDao extends JpaRepository { @Modifying @Query("delete from ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resid") void deleteByResourceId(@Param("resid") Long theResourcePid); + + @Query("SELECT t FROM ResourceIndexedSearchParamDate t WHERE t.myResourcePid = :resId") + List findAllForResourceId(@Param("resId") Long thePatientId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java index 32b1b10deca..00910347186 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java @@ -20,9 +20,8 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import org.springframework.data.jpa.repository.JpaRepository; - import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -32,6 +31,9 @@ import java.util.List; public interface IResourceIndexedSearchParamStringDao extends JpaRepository { @Modifying - @Query("delete from ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resid") - void deleteByResourceId(@Param("resid") Long theResourcePid); + @Query("DELETE FROM ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resId") + void deleteByResourceId(@Param("resId") Long theResourcePid); + + @Query("SELECT t FROM ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resId") + List findAllForResourceId(@Param("resId") Long thePatientId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java index 51b9059a34d..d9cf4628516 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceLinkDao.java @@ -20,16 +20,20 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import org.springframework.data.jpa.repository.JpaRepository; - import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface IResourceLinkDao extends JpaRepository { +import java.util.List; + +public interface IResourceLinkDao extends JpaRepository { @Modifying - @Query("delete from ResourceLink t WHERE t.mySourceResourcePid = :resid") - void deleteByResourceId(@Param("resid") Long theResourcePid); + @Query("DELETE FROM ResourceLink t WHERE t.mySourceResourcePid = :resId") + void deleteByResourceId(@Param("resId") Long theResourcePid); + + @Query("SELECT t FROM ResourceLink t WHERE t.mySourceResourcePid = :resId") + List findAllForResourceId(@Param("resId") Long thePatientId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java index 60f99e0dd71..9346f5a3c87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java @@ -8,9 +8,11 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; /* * #%L @@ -63,4 +65,12 @@ public interface IResourceTableDao extends JpaRepository { @Query("DELETE FROM ResourceTable t WHERE t.myId = :pid") void deleteByPid(@Param("pid") Long theId); + @Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid)") + Collection findLookupFieldsByResourcePid(@Param("pid") List thePids); + + @Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue = :partition_id") + Collection findLookupFieldsByResourcePidInPartition(@Param("pid") List thePids, @Param("partition_id") Integer thePartitionId); + + @Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IS NULL") + Collection findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List thePids); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java index cf1dbc4e7a8..a0bf58506eb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java @@ -37,12 +37,18 @@ public interface ISearchDao extends JpaRepository { @Query("SELECT s FROM Search s LEFT OUTER JOIN FETCH s.myIncludes WHERE s.myUuid = :uuid") Optional findByUuidAndFetchIncludes(@Param("uuid") String theUuid); - @Query("SELECT s.myId FROM Search s WHERE (s.myCreated < :cutoff) AND (s.myExpiryOrNull IS NULL OR s.myExpiryOrNull < :now)") + @Query("SELECT s.myId FROM Search s WHERE (s.myCreated < :cutoff) AND (s.myExpiryOrNull IS NULL OR s.myExpiryOrNull < :now) AND (s.myDeleted IS NULL OR s.myDeleted = FALSE)") Slice findWhereCreatedBefore(@Param("cutoff") Date theCutoff, @Param("now") Date theNow, Pageable thePage); + @Query("SELECT s.myId FROM Search s WHERE s.myDeleted = TRUE") + Slice findDeleted(Pageable thePage); + @Query("SELECT s FROM Search s WHERE s.myResourceType = :type AND mySearchQueryStringHash = :hash AND (s.myCreated > :cutoff) AND s.myDeleted = false AND s.myStatus <> 'FAILED'") Collection findWithCutoffOrExpiry(@Param("type") String theResourceType, @Param("hash") int theHashCode, @Param("cutoff") Date theCreatedCutoff); + @Query("SELECT COUNT(s) FROM Search s WHERE s.myDeleted = TRUE") + int countDeleted(); + @Modifying @Query("UPDATE Search s SET s.myDeleted = :deleted WHERE s.myId = :pid") void updateDeleted(@Param("pid") Long thePid, @Param("deleted") boolean theDeleted); @@ -50,4 +56,5 @@ public interface ISearchDao extends JpaRepository { @Modifying @Query("DELETE FROM Search s WHERE s.myId = :pid") void deleteByPid(@Param("pid") Long theId); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java index d965b5e2f88..30b9ec80fb3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java @@ -1,7 +1,13 @@ package ca.uhn.fhir.jpa.dao.data; -import java.util.Collection; -import java.util.Date; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; /* * #%L @@ -23,18 +29,10 @@ import java.util.Date; * #L% */ -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; - public interface ISearchParamPresentDao extends JpaRepository { @Query("SELECT s FROM SearchParamPresent s WHERE s.myResource = :res") - Collection findAllForResource(@Param("res") ResourceTable theResource); + List findAllForResource(@Param("res") ResourceTable theResource); @Modifying @Query("delete from SearchParamPresent t WHERE t.myResourcePid = :resid") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java index 6f367cefc91..906343e5377 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java @@ -1,11 +1,11 @@ package ca.uhn.fhir.jpa.dao.data; +import ca.uhn.fhir.jpa.dao.IHapiJpaRepository; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -33,7 +33,7 @@ import java.util.Optional; * #L% */ -public interface ITermConceptDao extends JpaRepository { +public interface ITermConceptDao extends IHapiJpaRepository { @Query("SELECT COUNT(t) FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid") Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid); @@ -50,4 +50,9 @@ public interface ITermConceptDao extends JpaRepository { @Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null") Page findResourcesRequiringReindexing(Pageable thePageRequest); + @Override + @Modifying + @Query("DELETE FROM TermConcept t WHERE t.myId = :pid") + void deleteByPid(@Param("pid") Long theId); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java index aee0d63d6f8..9fb79ba5911 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.dao.data; +import ca.uhn.fhir.jpa.dao.IHapiJpaRepository; import ca.uhn.fhir.jpa.entity.TermConceptDesignation; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -27,7 +28,7 @@ import org.springframework.data.repository.query.Param; * #L% */ -public interface ITermConceptDesignationDao extends JpaRepository { +public interface ITermConceptDesignationDao extends IHapiJpaRepository { @Query("SELECT t.myId FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid") Slice findIdsByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid); @@ -35,4 +36,9 @@ public interface ITermConceptDesignationDao extends JpaRepository { +public interface ITermConceptParentChildLinkDao extends IHapiJpaRepository { @Query("SELECT COUNT(t) FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid") Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid); @@ -39,4 +40,14 @@ public interface ITermConceptParentChildLinkDao extends JpaRepository findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); + + @Modifying + @Query("DELETE FROM TermConceptParentChildLink t WHERE t.myChildPid = :pid OR t.myParentPid = :pid") + void deleteByConceptPid(@Param("pid") Long theId); + + @Override + @Modifying + @Query("DELETE FROM TermConceptParentChildLink t WHERE t.myPid = :pid") + void deleteByPid(@Param("pid") Long theId); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java index c6fe9fdab43..9fe315fbf98 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.dao.data; +import ca.uhn.fhir.jpa.dao.IHapiJpaRepository; import ca.uhn.fhir.jpa.entity.TermConceptProperty; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -27,11 +28,17 @@ import org.springframework.data.repository.query.Param; * #L% */ -public interface ITermConceptPropertyDao extends JpaRepository { +public interface ITermConceptPropertyDao extends IHapiJpaRepository { @Query("SELECT t.myId FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid") Slice findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); @Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid") Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid); + + @Override + @Modifying + @Query("DELETE FROM TermConceptProperty t WHERE t.myId = :pid") + void deleteByPid(@Param("pid") Long theId); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 0947af92164..3b0bbe19a79 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -21,9 +21,9 @@ package ca.uhn.fhir.jpa.dao.dstu3; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; @@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; @@ -58,13 +57,12 @@ import static org.hl7.fhir.convertors.conv30_40.CodeSystem30_40.convertCodeSyste public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao implements IFhirResourceDaoCodeSystem { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCodeSystemDstu3.class); - + @Autowired + protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; @Autowired private ITermCodeSystemDao myCsDao; @Autowired - private ValidationSupportChain myValidationSupport; - @Autowired - protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; + private IValidationSupport myValidationSupport; @Autowired private FhirContext myFhirContext; @@ -74,7 +72,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { - Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest ); + Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); List valueSetIds = new ArrayList<>(); for (ResourcePersistentId next : ids) { IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "CodeSystem", next); @@ -85,7 +83,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -109,16 +107,16 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao implements IFhirResourceDaoEncounter { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java index 16b588a6711..27342afa06f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoMessageHeader; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader; import org.hl7.fhir.dstu3.model.MessageHeader; public class FhirResourceDaoMessageHeaderDstu3 extends BaseHapiFhirResourceDao implements IFhirResourceDaoMessageHeader { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java index b356bc1f040..665ac5f2889 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java @@ -20,24 +20,25 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; - +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; public class FhirResourceDaoPatientDstu3 extends BaseHapiFhirResourceDaoimplements IFhirResourceDaoPatient { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index 3041c843d8f..264ea62f065 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -1,12 +1,14 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import org.hl7.fhir.dstu3.model.*; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.Enumerations; +import org.hl7.fhir.dstu3.model.SearchParameter; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; @@ -70,8 +72,9 @@ public class FhirResourceDaoSearchParameterDstu3 extends BaseHapiFhirResourceDao String expression = theResource.getExpression(); FhirContext context = getContext(); Enumerations.SearchParamType type = theResource.getType(); + String code = theResource.getCode(); - FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); + FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoStructureDefinitionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoStructureDefinitionDstu3.java index 6ca56a41a12..5d62c9c0dc9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoStructureDefinitionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoStructureDefinitionDstu3.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.springframework.beans.factory.annotation.Autowired; @@ -34,7 +34,7 @@ public class FhirResourceDaoStructureDefinitionDstu3 extends BaseHapiFhirResourc @Override public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theName) { - StructureDefinition output = myValidationSupport.generateSnapshot(theInput, theUrl, theName); + StructureDefinition output = (StructureDefinition) myValidationSupport.generateSnapshot(myValidationSupport, theInput, theUrl, theName, null); Validate.notNull(output); return output; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java index 27814d0e92e..cc97721579a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java index 0bedcb6d141..2ab37c19930 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java @@ -20,10 +20,12 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; @@ -33,24 +35,20 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; import org.apache.commons.codec.binary.StringUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; import org.hl7.fhir.dstu3.model.IntegerType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -59,6 +57,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hl7.fhir.convertors.conv30_40.ValueSet30_40.convertValueSet; @@ -80,7 +79,7 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao listToValidate) { @@ -305,7 +262,7 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, + public IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { @@ -328,9 +285,9 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao contains = expansion.getExpansion().getContains(); @@ -377,10 +334,10 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao contains, String theSystem, String theCode, + private IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List contains, String theSystem, String theCode, Coding theCoding, CodeableConcept theCodeableConcept) { for (ValueSetExpansionContainsComponent nextCode : contains) { - ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept); + IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept); if (result != null) { return result; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java deleted file mode 100644 index 3281d6d5946..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java +++ /dev/null @@ -1,110 +0,0 @@ -package ca.uhn.fhir.jpa.dao.dstu3; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.r4.BaseJpaValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; -import java.util.Collections; -import java.util.List; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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% - */ - -@Transactional(value = TxType.REQUIRED) -public class JpaValidationSupportDstu3 extends BaseJpaValidationSupport implements IJpaValidationSupportDstu3 { - - /** - * Constructor - */ - public JpaValidationSupportDstu3() { - super(); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - if (isBlank(theSystem)) { - return null; - } - return fetchResource(theCtx, CodeSystem.class, theSystem); - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) { - if (isBlank(theSystem)) { - return null; - } - return fetchResource(theCtx, ValueSet.class, theSystem); - } - - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - return false; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java index bcdddb572fc..8d48ec550a2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java @@ -121,6 +121,7 @@ public class ExpungeEverythingService { counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class)); counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class)); counter.addAndGet(expungeEverythingByType(ResourceTable.class)); + counter.addAndGet(expungeEverythingByType(PartitionEntity.class)); myTxTemplate.execute(t -> { counter.addAndGet(doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d")); return null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeOperation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeOperation.java index 25fc0735562..7da4a0cc0fc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeOperation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeOperation.java @@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.dao.expunge; */ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java index 1a7c3ea4bc0..0eae943926a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java @@ -20,12 +20,11 @@ package ca.uhn.fhir.jpa.dao.expunge; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java index 0af9dd63261..8e2e450ca62 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.dao.expunge; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.StopWatch; import com.google.common.collect.Lists; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java index b66dc9b048f..2171c9b89d3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java @@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.model.entity.ForcedId; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java index 072e98b7470..88c86d150bd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java @@ -26,10 +26,11 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -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.model.cross.ResourcePersistentId; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -41,7 +42,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import javax.annotation.Nullable; import javax.persistence.EntityManager; @@ -49,7 +49,6 @@ import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import java.util.Optional; -@Service public class DaoResourceLinkResolver implements IResourceLinkResolver { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class); @PersistenceContext(type = PersistenceContextType.TRANSACTION) @@ -64,17 +63,16 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver { private DaoRegistry myDaoRegistry; @Override - public ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest) { - ResourceTable target; - ResourcePersistentId valueOf; - String idPart = theNextId.getIdPart(); + public IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class theType, IBaseReference theReference, RequestDetails theRequest) { + IResourceLookup resolvedResource; + String idPart = theSourceResourceId.getIdPart(); try { - valueOf = myIdHelperService.translateForcedIdToPid(theTypeString, idPart, theRequest); - ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, valueOf); + resolvedResource = myIdHelperService.resolveResourceIdentity(theRequestPartitionId, theResourceType, idPart, theRequest); + ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource); } catch (ResourceNotFoundException e) { - Optional pidOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart); - if (!pidOpt.isPresent()) { + Optional createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart); + if (!createdTableOpt.isPresent()) { if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) { return null; @@ -82,43 +80,36 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver { RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType); String resName = missingResourceDef.getName(); - throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theNextPathsUnsplit); + throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theSourcePath); } - valueOf = pidOpt.get(); + resolvedResource = createdTableOpt.get(); } - target = myEntityManager.find(ResourceTable.class, valueOf.getIdAsLong()); - RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(theType); - if (target == null) { - String resName = targetResourceDef.getName(); - throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theNextPathsUnsplit); + ourLog.trace("Resolved resource of type {} as PID: {}", resolvedResource.getResourceType(), resolvedResource.getResourceId()); + if (!theResourceType.equals(resolvedResource.getResourceType())) { + ourLog.error("Resource with PID {} was of type {} and wanted {}", resolvedResource.getResourceId(), theResourceType, resolvedResource.getResourceType()); + throw new UnprocessableEntityException("Resource contains reference to unknown resource ID " + theSourceResourceId.getValue()); } - ourLog.trace("Resource PID {} is of type {}", valueOf, target.getResourceType()); - if (!theTypeString.equals(target.getResourceType())) { - ourLog.error("Resource {} with PID {} was not of type {}", target.getIdDt().getValue(), target.getId(), theTypeString); - throw new UnprocessableEntityException( - "Resource contains reference to " + theNextId.getValue() + " but resource with ID " + theNextId.getIdPart() + " is actually of type " + target.getResourceType()); + if (resolvedResource.getDeleted() != null) { + String resName = resolvedResource.getResourceType(); + throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theSourcePath); } - if (target.getDeleted() != null) { - String resName = targetResourceDef.getName(); - throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theNextPathsUnsplit); - } - - if (!theNextSpDef.hasTargets() && theNextSpDef.getTargets().contains(theTypeString)) { + if (!theSearchParam.hasTargets() && theSearchParam.getTargets().contains(theResourceType)) { return null; } - return target; + + return resolvedResource; } /** * @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID */ - public Optional createPlaceholderTargetIfConfiguredToDoSo(Class theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder) { - ResourcePersistentId valueOf = null; + public Optional createPlaceholderTargetIfConfiguredToDoSo(Class theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder) { + ResourceTable valueOf = null; if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) { RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType); @@ -136,9 +127,9 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver { if (theIdToAssignToPlaceholder != null) { newResource.setId(resName + "/" + theIdToAssignToPlaceholder); - valueOf = placeholderResourceDao.update(newResource).getEntity().getPersistentId(); + valueOf = ((ResourceTable) placeholderResourceDao.update(newResource).getEntity()); } else { - valueOf = placeholderResourceDao.create(newResource).getEntity().getPersistentId(); + valueOf = ((ResourceTable) placeholderResourceDao.create(newResource).getEntity()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java index a5b96de34b1..11910a1e6ff 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java @@ -20,8 +20,9 @@ package ca.uhn.fhir.jpa.dao.index; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.util.AddRemoveCount; import org.springframework.beans.factory.annotation.Autowired; @@ -36,23 +37,22 @@ import java.util.List; @Service public class DaoSearchParamSynchronizer { - @Autowired - private DaoConfig myDaoConfig; - @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; + @Autowired + private DaoConfig myDaoConfig; public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) { AddRemoveCount retVal = new AddRemoveCount(); synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams); synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams); - synchronize(theParams, theEntity,retVal, theParams.myNumberParams, existingParams.myNumberParams); - synchronize(theParams, theEntity,retVal, theParams.myQuantityParams, existingParams.myQuantityParams); - synchronize(theParams, theEntity,retVal, theParams.myDateParams, existingParams.myDateParams); - synchronize(theParams, theEntity,retVal, theParams.myUriParams, existingParams.myUriParams); + synchronize(theParams, theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams); + synchronize(theParams, theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams); + synchronize(theParams, theEntity, retVal, theParams.myDateParams, existingParams.myDateParams); + synchronize(theParams, theEntity, retVal, theParams.myUriParams, existingParams.myUriParams); synchronize(theParams, theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams); - synchronize(theParams, theEntity,retVal, theParams.myLinks, existingParams.myLinks); + synchronize(theParams, theEntity, retVal, theParams.myLinks, existingParams.myLinks); // make sure links are indexed theEntity.setResourceLinks(theParams.myLinks); @@ -61,7 +61,6 @@ public class DaoSearchParamSynchronizer { } private void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, AddRemoveCount theAddRemoveCount, Collection theNewParms, Collection theExistingParms) { - theParams.calculateHashes(theNewParms); List quantitiesToRemove = subtract(theExistingParms, theNewParms); List quantitiesToAdd = subtract(theNewParms, theExistingParms); tryToReuseIndexEntities(quantitiesToRemove, quantitiesToAdd); @@ -69,6 +68,10 @@ public class DaoSearchParamSynchronizer { myEntityManager.remove(next); theEntity.getParamsQuantity().remove(next); } + for (T next : quantitiesToAdd) { + next.setPartitionId(theEntity.getPartitionId()); + } + theParams.calculateHashes(theNewParms); for (T next : quantitiesToAdd) { myEntityManager.merge(next); } @@ -85,7 +88,7 @@ public class DaoSearchParamSynchronizer { * "one delete + one insert" with "one update" * * @param theIndexesToRemove The rows that would be removed - * @param theIndexesToAdd The rows that would be added + * @param theIndexesToAdd The rows that would be added */ private void tryToReuseIndexEntities(List theIndexesToRemove, List theIndexesToAdd) { for (int addIndex = 0; addIndex < theIndexesToAdd.size(); addIndex++) { @@ -102,13 +105,12 @@ public class DaoSearchParamSynchronizer { // Take a row we were going to remove, and repurpose its ID T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1); - targetEntity.setId(entityToReuse.getId()); + entityToReuse.copyMutableValuesFrom(targetEntity); + theIndexesToAdd.set(addIndex, entityToReuse); } } - - List subtract(Collection theSubtractFrom, Collection theToSubtract) { assert theSubtractFrom != theToSubtract; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index a8e7cc82bcd..49f9c9e18b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -21,133 +21,223 @@ package ca.uhn.fhir.jpa.dao.index; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.interceptor.api.HookParams; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; -import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; -import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.model.cross.IResourceLookup; +import ca.uhn.fhir.jpa.model.cross.ResourceLookup; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; +import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.checkerframework.checker.nullness.qual.NonNull; import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.stereotype.Service; import javax.annotation.Nonnull; -import java.util.*; +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +/** + * This class is used to convert between PIDs (the internal primary key for a particular resource as + * stored in the {@link ca.uhn.fhir.jpa.model.entity.ResourceTable HFJ_RESOURCE} table), and the + * public ID that a resource has. + *

        + * These IDs are sometimes one and the same (by default, a resource that the server assigns the ID of + * Patient/1 will simply use a PID of 1 and and ID of 1. However, they may also be different + * in cases where a forced ID is used (an arbitrary client-assigned ID). + *

        + *

        + * This service is highly optimized in order to minimize the number of DB calls as much as possible, + * since ID resolution is fundamental to many basic operations. This service returns either + * {@link IResourceLookup} or {@link ResourcePersistentId} depending on the method being called. + * The former involves an extra database join that the latter does not require, so selecting the + * right method here is important. + *

        + */ @Service public class IdHelperService { + private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class); + @Autowired protected IForcedIdDao myForcedIdDao; + @Autowired + protected IResourceTableDao myResourceTableDao; @Autowired(required = true) private DaoConfig myDaoConfig; @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private FhirContext myFhirCtx; + + private Cache myPersistentIdCache; + private Cache myResourceLookupCache; + + @PostConstruct + public void start() { + myPersistentIdCache = newCache(); + myResourceLookupCache = newCache(); + } + public void delete(ForcedId forcedId) { myForcedIdDao.deleteByPid(forcedId.getId()); } /** + * Given a forced ID, convert it to it's Long value. Since you are allowed to use string IDs for resources, we need to + * convert those to the underlying Long values that are stored, for lookup and comparison purposes. + * * @throws ResourceNotFoundException If the ID can not be found */ @Nonnull - public ResourcePersistentId translateForcedIdToPid(IIdType theId, RequestDetails theRequestDetails) { - return translateForcedIdToPid(theId.getResourceType(), theId.getIdPart(), theRequestDetails); - } - - /** - * @throws ResourceNotFoundException If the ID can not be found - */ - @Nonnull - public ResourcePersistentId translateForcedIdToPid(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException { + public IResourceLookup resolveResourceIdentity(RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException { // We only pass 1 input in so only 0..1 will come back - IdDt id = new IdDt(theResourceName, theResourceId); - List matches = translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, Collections.singletonList(id)); + IdDt id = new IdDt(theResourceType, theResourceId); + Collection matches = translateForcedIdToPids(theRequestPartitionId, theRequestDetails, Collections.singletonList(id)); assert matches.size() <= 1; if (matches.isEmpty()) { throw new ResourceNotFoundException(id); } - return matches.get(0); + return matches.iterator().next(); } - public List translateForcedIdToPids(Collection theId, RequestDetails theRequestDetails) { - return IdHelperService.translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, theId); + /** + * Given a resource type and ID, determines the internal persistent ID for the resource. + * + * @throws ResourceNotFoundException If the ID can not be found + */ + @Nonnull + public ResourcePersistentId resolveResourcePersistentIds(RequestPartitionId theRequestPartitionId, String theResourceType, String theId) { + Long retVal; + if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) { + if (myDaoConfig.isDeleteEnabled()) { + retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, theId); + } else { + String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId; + retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(theRequestPartitionId, theResourceType, theId)); + } + + } else { + retVal = Long.parseLong(theId); + } + + return new ResourcePersistentId(retVal); } - private static List translateForcedIdToPids(DaoConfig theDaoConfig, IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IForcedIdDao theForcedIdDao, Collection theId) { - theId.forEach(id -> Validate.isTrue(id.hasIdPart())); + /** + * Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs. + *

        + * This implementation will always try to use a cache for performance, meaning that it can resolve resources that + * are deleted (but note that forced IDs can't change, so the cache can't return incorrect results) + */ + @Nonnull + public List resolveResourcePersistentIdsWithCache(RequestPartitionId theRequestPartitionId, List theIds, RequestDetails theRequest) { + theIds.forEach(id -> Validate.isTrue(id.hasIdPart())); - if (theId.isEmpty()) { + if (theIds.isEmpty()) { return Collections.emptyList(); } List retVal = new ArrayList<>(); - ListMultimap typeToIds = MultimapBuilder.hashKeys().arrayListValues().build(); - for (IIdType nextId : theId) { - if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(nextId)) { - retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong())); - } else { - if (nextId.hasResourceType()) { - typeToIds.put(nextId.getResourceType(), nextId.getIdPart()); - } else { - typeToIds.put("", nextId.getIdPart()); - } - } + if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) { + theIds + .stream() + .filter(IdHelperService::isValidPid) + .map(IIdType::getIdPartAsLong) + .map(ResourcePersistentId::new) + .forEach(retVal::add); } + ListMultimap typeToIds = organizeIdsByResourceType(theIds); + for (Map.Entry> nextEntry : typeToIds.asMap().entrySet()) { String nextResourceType = nextEntry.getKey(); Collection nextIds = nextEntry.getValue(); if (isBlank(nextResourceType)) { - StorageProcessingMessage msg = new StorageProcessingMessage() - .setMessage("This search uses unqualified resource IDs (an ID without a resource type). This is less efficient than using a qualified type."); - HookParams params = new HookParams() - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest) - .add(StorageProcessingMessage.class, msg); - JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); - - theForcedIdDao - .findByForcedId(nextIds) - .stream() - .map(t->new ResourcePersistentId(t)) - .forEach(t->retVal.add(t)); + List views = myForcedIdDao.findByForcedId(nextIds); + views.forEach(t -> retVal.add(new ResourcePersistentId(t))); } else { - theForcedIdDao - .findByTypeAndForcedId(nextResourceType, nextIds) - .stream() - .map(t->new ResourcePersistentId(t)) - .forEach(t->retVal.add(t)); + for (Iterator idIterator = nextIds.iterator(); idIterator.hasNext(); ) { + String nextId = idIterator.next(); + String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + nextResourceType + "/" + nextId; + Long nextCachedPid = myPersistentIdCache.getIfPresent(key); + if (nextCachedPid != null) { + idIterator.remove(); + retVal.add(new ResourcePersistentId(nextCachedPid)); + } + } + + if (nextIds.size() > 0) { + + Collection views; + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() != null) { + views = myForcedIdDao.findByTypeAndForcedIdInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId()); + } else { + views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds); + } + } else { + views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds); + } + for (Object[] nextView : views) { + String forcedId = (String) nextView[0]; + Long pid = (Long) nextView[1]; + retVal.add(new ResourcePersistentId(pid)); + + String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + nextResourceType + "/" + forcedId; + myPersistentIdCache.put(key, pid); + } + } } } - return retVal; + return retVal; } + /** + * Given a persistent ID, returns the associated resource ID + */ + @Nonnull public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) { IIdType retVal = theCtx.getVersion().newIdType(); retVal.setValue(translatePidIdToForcedId(theResourceType, theId)); return retVal; } - public String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) { + private String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) { ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong()); if (forcedId != null) { return forcedId.getResourceType() + '/' + forcedId.getForcedId(); @@ -156,17 +246,159 @@ public class IdHelperService { } } - public static boolean isValidPid(IIdType theId) { - if (theId == null || theId.getIdPart() == null) { - return false; - } - String idPart = theId.getIdPart(); - for (int i = 0; i < idPart.length(); i++) { - char nextChar = idPart.charAt(i); - if (nextChar < '0' || nextChar > '9') { - return false; + private ListMultimap organizeIdsByResourceType(Collection theIds) { + ListMultimap typeToIds = MultimapBuilder.hashKeys().arrayListValues().build(); + for (IIdType nextId : theIds) { + if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(nextId)) { + if (nextId.hasResourceType()) { + typeToIds.put(nextId.getResourceType(), nextId.getIdPart()); + } else { + typeToIds.put("", nextId.getIdPart()); + } } } - return true; + return typeToIds; + } + + private Long resolveResourceIdentity(@Nullable RequestPartitionId theRequestPartitionId, @Nonnull String theResourceType, @Nonnull String theId) { + Optional pid; + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() == null) { + pid = myForcedIdDao.findByPartitionIdNullAndTypeAndForcedId(theResourceType, theId); + } else { + pid = myForcedIdDao.findByPartitionIdAndTypeAndForcedId(theRequestPartitionId.getPartitionId(), theResourceType, theId); + } + } else { + try { + pid = myForcedIdDao.findByTypeAndForcedId(theResourceType, theId); + } catch (IncorrectResultSizeDataAccessException e) { + /* + * This means that: + * 1. There are two resources with the exact same resource type and forced id + * 2. The unique constraint on this column-pair has been dropped + */ + String msg = myFhirCtx.getLocalizer().getMessage(IdHelperService.class, "nonUniqueForcedId"); + throw new PreconditionFailedException(msg); + } + } + + if (!pid.isPresent()) { + throw new ResourceNotFoundException(new IdDt(theResourceType, theId)); + } + return pid.get(); + } + + private Collection translateForcedIdToPids(RequestPartitionId theRequestPartitionId, RequestDetails theRequest, Collection theId) { + theId.forEach(id -> Validate.isTrue(id.hasIdPart())); + + if (theId.isEmpty()) { + return Collections.emptyList(); + } + + List retVal = new ArrayList<>(); + + if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) { + List pids = theId + .stream() + .filter(t -> isValidPid(t)) + .map(t -> t.getIdPartAsLong()) + .collect(Collectors.toList()); + if (!pids.isEmpty()) { + resolvePids(theRequestPartitionId, pids, retVal); + } + } + + ListMultimap typeToIds = organizeIdsByResourceType(theId); + for (Map.Entry> nextEntry : typeToIds.asMap().entrySet()) { + String nextResourceType = nextEntry.getKey(); + Collection nextIds = nextEntry.getValue(); + + if (!myDaoConfig.isDeleteEnabled()) { + for (Iterator forcedIdIterator = nextIds.iterator(); forcedIdIterator.hasNext(); ) { + String nextForcedId = forcedIdIterator.next(); + String nextKey = nextResourceType + "/" + nextForcedId; + IResourceLookup cachedLookup = myResourceLookupCache.getIfPresent(nextKey); + if (cachedLookup != null) { + forcedIdIterator.remove(); + retVal.add(cachedLookup); + } + } + } + + if (nextIds.size() > 0) { + Collection views; + assert isNotBlank(nextResourceType); + + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() != null) { + views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionId()); + } else { + views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds); + } + } else { + views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds); + } + + for (Object[] next : views) { + String resourceType = (String) next[0]; + Long resourcePid = (Long) next[1]; + String forcedId = (String) next[2]; + Date deletedAt = (Date) next[3]; + ResourceLookup lookup = new ResourceLookup(resourceType, resourcePid, deletedAt); + retVal.add(lookup); + + if (!myDaoConfig.isDeleteEnabled()) { + String key = resourceType + "/" + forcedId; + myResourceLookupCache.put(key, lookup); + } + } + } + + } + + return retVal; + } + + private void resolvePids(RequestPartitionId theRequestPartitionId, List thePidsToResolve, List theTarget) { + Collection lookup; + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() != null) { + lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartition(thePidsToResolve, theRequestPartitionId.getPartitionId()); + } else { + lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(thePidsToResolve); + } + } else { + lookup = myResourceTableDao.findLookupFieldsByResourcePid(thePidsToResolve); + } + lookup + .stream() + .map(t -> new ResourceLookup((String) t[0], (Long) t[1], (Date) t[2])) + .forEach(theTarget::add); + } + + public void clearCache() { + myPersistentIdCache.invalidateAll(); + myResourceLookupCache.invalidateAll(); + } + + private @NonNull Cache newCache() { + return Caffeine + .newBuilder() + .maximumSize(10000) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(); + } + + public static boolean isValidPid(IIdType theId) { + if (theId == null) { + return false; + } + + String idPart = theId.getIdPart(); + return isValidPid(idPart); + } + + public static boolean isValidPid(String theIdPart) { + return StringUtils.isNumeric(theIdPart); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index a540c0ab1b3..bc8413c2185 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -23,10 +23,11 @@ package ca.uhn.fhir.jpa.dao.index; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.MatchResourceUrlService; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; @@ -34,7 +35,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -70,7 +70,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Lazy public class SearchParamWithInlineReferencesExtractor { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamWithInlineReferencesExtractor.class); - + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; @Autowired private MatchResourceUrlService myMatchResourceUrlService; @Autowired @@ -84,31 +85,24 @@ public class SearchParamWithInlineReferencesExtractor { @Autowired private SearchParamExtractorService mySearchParamExtractorService; @Autowired - private ResourceLinkExtractor myResourceLinkExtractor; - @Autowired private DaoResourceLinkResolver myDaoResourceLinkResolver; @Autowired private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer; @Autowired private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; - - @PersistenceContext(type = PersistenceContextType.TRANSACTION) - protected EntityManager myEntityManager; + @Autowired + private PartitionSettings myPartitionSettings; public void populateFromResource(ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) { - mySearchParamExtractorService.extractFromResource(theRequest, theParams, theEntity, theResource); + extractInlineReferences(theResource, theRequest); + + mySearchParamExtractorService.extractFromResource(theEntity.getPartitionId(), theRequest, theParams, theEntity, theResource, theUpdateTime, true); Set> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) { - theParams.findMissingSearchParams(myDaoConfig.getModelConfig(), theEntity, activeSearchParams); + theParams.findMissingSearchParams(myPartitionSettings, myDaoConfig.getModelConfig(), theEntity, activeSearchParams); } - theParams.setUpdatedTime(theUpdateTime); - - extractInlineReferences(theResource, theRequest); - - myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver, true, theRequest); - /* * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them */ @@ -187,7 +181,9 @@ public class SearchParamWithInlineReferencesExtractor { if (linksForCompositePart != null) { for (ResourceLink nextLink : linksForCompositePart) { if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) { - String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue(); + assert isNotBlank(nextLink.getTargetResourceType()); + assert isNotBlank(nextLink.getTargetResourceId()); + String value = nextLink.getTargetResourceType() + "/" + nextLink.getTargetResourceId(); if (isNotBlank(value)) { value = UrlUtil.escapeUrlParam(value); nextChoicesList.add(key + "=" + value); @@ -209,7 +205,6 @@ public class SearchParamWithInlineReferencesExtractor { } - /** * Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the * matching resource. @@ -250,13 +245,14 @@ public class SearchParamWithInlineReferencesExtractor { ResourcePersistentId match; if (matches.isEmpty()) { - Optional placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null); + Optional placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null); if (placeholderOpt.isPresent()) { - match = placeholderOpt.get(); + match = new ResourcePersistentId(placeholderOpt.get().getResourceId()); } else { String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue()); throw new ResourceNotFoundException(msg); } + } else if (matches.size() > 1) { String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue()); throw new PreconditionFailedException(msg); @@ -264,9 +260,9 @@ public class SearchParamWithInlineReferencesExtractor { match = matches.iterator().next(); } - String newId = myIdHelperService.translatePidIdToForcedId(resourceTypeString, match); + IIdType newId = myIdHelperService.translatePidIdToForcedId(myContext, resourceTypeString, match); ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId); - nextRef.setReference(newId); + nextRef.setReference(newId.getValue()); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java index 6c613e684bb..1015b7d24c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java @@ -21,9 +21,11 @@ package ca.uhn.fhir.jpa.dao.predicate; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IDao; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchBuilder; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.BasePartitionable; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -38,30 +40,34 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; -import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Predicate; import java.math.BigDecimal; import java.math.MathContext; +import java.util.ArrayList; import java.util.List; abstract class BasePredicateBuilder { private static final Logger ourLog = LoggerFactory.getLogger(BasePredicateBuilder.class); - @Autowired - FhirContext myContext; - @Autowired - DaoConfig myDaoConfig; - - - boolean myDontUseHashesForSearch; - final IDao myCallingDao; - final CriteriaBuilder myBuilder; + final CriteriaBuilder myCriteriaBuilder; final QueryRoot myQueryRoot; final Class myResourceType; final String myResourceName; final SearchParameterMap myParams; + @Autowired + FhirContext myContext; + @Autowired + DaoConfig myDaoConfig; + boolean myDontUseHashesForSearch; + @Autowired + private PartitionSettings myPartitionSettings; BasePredicateBuilder(SearchBuilder theSearchBuilder) { - myCallingDao = theSearchBuilder.getCallingDao(); - myBuilder = theSearchBuilder.getBuilder(); + myCriteriaBuilder = theSearchBuilder.getBuilder(); myQueryRoot = theSearchBuilder.getQueryRoot(); myResourceType = theSearchBuilder.getResourceType(); myResourceName = theSearchBuilder.getResourceName(); @@ -109,40 +115,57 @@ abstract class BasePredicateBuilder { return (Join) join; } - void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) { -// if (myDontUseHashesForSearch) { -// Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT); -// Join paramJoin = paramPresentJoin.join("mySearchParam", JoinType.LEFT); -// -// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName)); -// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myParamName"), theParamName)); -// myQueryRoot.addPredicate(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing)); -// } - + void addPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, RequestPartitionId theRequestPartitionId) { Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT); Expression hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class); - Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing); - myQueryRoot.addPredicate(myBuilder.equal(hashPresence, hash)); + Long hash = SearchParamPresent.calculateHashPresence(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName, !theMissing); + + List predicates = new ArrayList<>(); + predicates.add(myCriteriaBuilder.equal(hashPresence, hash)); + + addPartitionIdPredicate(theRequestPartitionId, paramPresentJoin, predicates); + + myQueryRoot.setHasIndexJoins(); + myQueryRoot.addPredicates(predicates); } - void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join theJoin) { - myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myResourceType"), theResourceName)); - myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myParamName"), theParamName)); - myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myMissing"), theMissing)); + void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, Join theJoin, RequestPartitionId theRequestPartitionId) { + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() != null) { + myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId())); + } else { + myQueryRoot.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue"))); + } + } + myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName)); + myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName)); + myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing)); + myQueryRoot.setHasIndexJoins(); } - Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From theFrom, Predicate thePredicate) { + Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From theFrom, Predicate thePredicate, RequestPartitionId theRequestPartitionId) { + List andPredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, theFrom, andPredicates); + if (myDontUseHashesForSearch) { - Predicate resourceTypePredicate = myBuilder.equal(theFrom.get("myResourceType"), theResourceName); - Predicate paramNamePredicate = myBuilder.equal(theFrom.get("myParamName"), theParamName); - Predicate outerPredicate = myBuilder.and(resourceTypePredicate, paramNamePredicate, thePredicate); - return outerPredicate; + Predicate resourceTypePredicate = myCriteriaBuilder.equal(theFrom.get("myResourceType"), theResourceName); + Predicate paramNamePredicate = myCriteriaBuilder.equal(theFrom.get("myParamName"), theParamName); + andPredicates.add(resourceTypePredicate); + andPredicates.add(paramNamePredicate); + andPredicates.add(thePredicate); + } else { + long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName); + Predicate hashIdentityPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity); + andPredicates.add(hashIdentityPredicate); + andPredicates.add(thePredicate); } - long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName); - Predicate hashIdentityPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity); - return myBuilder.and(hashIdentityPredicate, thePredicate); + return myCriteriaBuilder.and(toArray(andPredicates)); + } + + public PartitionSettings getPartitionSettings() { + return myPartitionSettings; } Predicate createPredicateNumeric(String theResourceName, @@ -153,7 +176,7 @@ abstract class BasePredicateBuilder { ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression thePath, - String invalidMessageName) { + String invalidMessageName, RequestPartitionId theRequestPartitionId) { Predicate num; // Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to // use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator. @@ -197,7 +220,20 @@ abstract class BasePredicateBuilder { if (theParamName == null) { return num; } - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num, theRequestPartitionId); + } + + void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From theJoin, List theCodePredicates) { + if (theRequestPartitionId != null) { + Integer partitionId = theRequestPartitionId.getPartitionId(); + Predicate partitionPredicate; + if (partitionId != null) { + partitionPredicate = myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue").as(Integer.class), partitionId); + } else { + partitionPredicate = myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue").as(Integer.class)); + } + myQueryRoot.addPredicate(partitionPredicate); + } } static String createLeftAndRightMatchLikeExpression(String likeExpression) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java index f7cd30be947..385770634b3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.model.api.IQueryParameterType; import javax.annotation.Nullable; @@ -31,5 +32,6 @@ public interface IPredicateBuilder { Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation); + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java index 1de807105f4..69d77e3dc0a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -55,75 +56,75 @@ public class PredicateBuilder { myPredicateBuilderUri = thePredicateBuilderFactory.newPredicateBuilderUri(theSearchBuilder); } - void addPredicateCoords(String theResourceName, String theParamName, List theNextAnd) { - myPredicateBuilderCoords.addPredicate(theResourceName, theParamName, theNextAnd, null); + void addPredicateCoords(String theResourceName, String theParamName, List theNextAnd, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderCoords.addPredicate(theResourceName, theParamName, theNextAnd, null, theRequestPartitionId); } - Predicate addPredicateDate(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderDate.addPredicate(theResourceName, theParamName, theNextAnd, theOperation); + Predicate addPredicateDate(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderDate.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateNumber(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderNumber.addPredicate(theResourceName, theParamName, theNextAnd, theOperation); + Predicate addPredicateNumber(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderNumber.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateQuantity(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderQuantity.addPredicate(theResourceName, theParamName, theNextAnd, theOperation); + Predicate addPredicateQuantity(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderQuantity.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); } - void addPredicateString(String theResourceName, String theParamName, List theNextAnd) { - myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, SearchFilterParser.CompareOperation.sw); + void addPredicateString(String theResourceName, String theParamName, List theNextAnd, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); } - Predicate addPredicateString(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation); + Predicate addPredicateString(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); } - void addPredicateTag(List> theAndOrParams, String theParamName) { - myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName); + void addPredicateTag(List> theAndOrParams, String theParamName, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId); } - Predicate addPredicateToken(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderToken.addPredicate(theResourceName, theParamName, theNextAnd, theOperation); + Predicate addPredicateToken(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderToken.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateUri(String theResourceName, String theName, List theSingletonList, SearchFilterParser.CompareOperation theOperation) { - return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation); + Predicate addPredicateUri(String theResourceName, String theName, List theSingletonList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation, theRequestPartitionId); } - public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest) { - myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest); + public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, theRequestPartitionId); } - Subquery createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList theOrValues, RequestDetails theRequest) { - return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest); + Subquery createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, theRequestPartitionId); } Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join theJoin) { return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin); } - void addPredicateResourceId(List> theAndOrParams, String theResourceName, RequestDetails theRequest) { - myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequest); + void addPredicateResourceId(List> theAndOrParams, String theResourceName, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequestPartitionId); } - public Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { - return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequest); + public Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequestPartitionId); } - Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theStringJoin) { - return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theName, theBuilder, theStringJoin); + Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theStringJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theName, theBuilder, theStringJoin, theRequestPartitionId); } - Collection createPredicateToken(List theTokens, String theResourceName, String theName, CriteriaBuilder theBuilder, From theTokenJoin) { - return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theName, theBuilder, theTokenJoin); + Collection createPredicateToken(List theTokens, String theResourceName, String theName, CriteriaBuilder theBuilder, From theTokenJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theName, theBuilder, theTokenJoin, theRequestPartitionId); } - Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theDateJoin) { - return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin); + Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theDateJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId); } - Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theDateJoin) { - return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin); + Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theDateJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java index 2cf5ac68ddc..2110d9f1cf0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -58,7 +59,8 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + From theFrom, + RequestPartitionId theRequestPartitionId) { String latitudeValue; String longitudeValue; Double distanceKm = 0.0; @@ -119,7 +121,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box); } Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate); - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From theFrom, SearchBox theBox) { @@ -147,28 +149,32 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation theOperation) { + SearchFilterParser.CompareOperation theOperation, + RequestPartitionId theRequestPartitionId) { Join join = createJoin(SearchBuilderJoinEnum.COORDS, theParamName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } - List codePredicates = new ArrayList(); + List codePredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + for (IQueryParameterType nextOr : theList) { Predicate singleCode = createPredicateCoords(nextOr, theResourceName, theParamName, - myBuilder, - join - ); + myCriteriaBuilder, + join, + theRequestPartitionId); codePredicates.add(singleCode); } - Predicate retVal = myBuilder.or(toArray(codePredicates)); + Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); myQueryRoot.addPredicate(retVal); + myQueryRoot.setHasIndexJoins(); return retVal; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java index cb80ed64a5e..d999c293603 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -60,7 +60,8 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { boolean newJoin = false; if (myJoinMap == null) { @@ -77,26 +78,26 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi if (theList.get(0).getMissing() != null) { Boolean missing = theList.get(0).getMissing(); - addPredicateParamMissing(theResourceName, theParamName, missing, join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, missing, join, theRequestPartitionId); return null; } List codePredicates = new ArrayList<>(); + for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; - Predicate p = createPredicateDate(params, - theResourceName, - theParamName, - myBuilder, + Predicate p = createPredicateDate(nextOr, + myCriteriaBuilder, join, - operation); + operation + ); codePredicates.add(p); } - Predicate orPredicates = myBuilder.or(toArray(codePredicates)); + Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates)); + myQueryRoot.setHasIndexJoins(); if (newJoin) { - Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates); + Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates, theRequestPartitionId); myQueryRoot.addPredicate(identityAndValuePredicate); } else { myQueryRoot.addPredicate(orPredicates); @@ -109,19 +110,17 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + From theFrom, + RequestPartitionId theRequestPartitionId) { Predicate predicateDate = createPredicateDate(theParam, - theResourceName, - theParamName, theBuilder, theFrom, - null); - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate); + null + ); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate, theRequestPartitionId); } private Predicate createPredicateDate(IQueryParameterType theParam, - String theResourceName, - String theParamName, CriteriaBuilder theBuilder, From theFrom, SearchFilterParser.CompareOperation theOperation) { @@ -155,6 +154,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi private boolean isNullOrDayPrecision(DateParam theDateParam) { return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal(); } + private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder, From theFrom, DateRangeParam theRange, diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java index d989546d3f3..0194343d205 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -53,16 +54,19 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { Join join = createJoin(SearchBuilderJoinEnum.NUMBER, theParamName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } List codePredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + for (IQueryParameterType nextOr : theList) { if (nextOr instanceof NumberParam) { @@ -94,8 +98,8 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB String invalidMessageName = "invalidNumberPrefix"; - Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, nextOr, prefix, value, fromObj, invalidMessageName); - Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric); + Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, theRequestPartitionId); + Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric, theRequestPartitionId); codePredicates.add(predicateOuter); } else { @@ -104,7 +108,8 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB } - Predicate predicate = myBuilder.or(toArray(codePredicates)); + Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates)); + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(predicate); return predicate; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java index 330059e1d86..9c40570dd85 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; @@ -31,7 +32,11 @@ import ca.uhn.fhir.rest.param.QuantityParam; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Predicate; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -51,28 +56,26 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation theOperation, + RequestPartitionId theRequestPartitionId) { Join join = createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } List codePredicates = new ArrayList(); - for (IQueryParameterType nextOr : theList) { + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); - Predicate singleCode = createPredicateQuantity(nextOr, - theResourceName, - theParamName, - myBuilder, - join, - operation); + for (IQueryParameterType nextOr : theList) { + Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); codePredicates.add(singleCode); } - Predicate retVal = myBuilder.or(toArray(codePredicates)); + Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(retVal); return retVal; } @@ -81,13 +84,15 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + From theFrom, + RequestPartitionId theRequestPartitionId) { return createPredicateQuantity(theParam, theResourceName, theParamName, theBuilder, theFrom, - null); + null, + theRequestPartitionId); } private Predicate createPredicateQuantity(IQueryParameterType theParam, @@ -95,7 +100,8 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat String theParamName, CriteriaBuilder theBuilder, From theFrom, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { String systemValue; String unitsValue; ParamPrefixEnum cmpValue = null; @@ -152,7 +158,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat final Expression path = theFrom.get("myValue"); String invalidMessageName = "invalidQuantityPrefix"; - Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName); + Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, theRequestPartitionId); Predicate singleCode; if (system == null && code == null) { @@ -165,26 +171,26 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat singleCode = theBuilder.and(system, code, num); } - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } Predicate hashPredicate; if (!isBlank(systemValue) && !isBlank(unitsValue)) { - long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue); - hashPredicate = myBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash); + long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue); + hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash); } else if (!isBlank(unitsValue)) { - long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(theResourceName, theParamName, unitsValue); - hashPredicate = myBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash); + long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue); + hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash); } else { - long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName); - hashPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hash); + long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName); + hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hash); } cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL); final Expression path = theFrom.get("myValue"); String invalidMessageName = "invalidQuantityPrefix"; - Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName); + Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName, theRequestPartitionId); return theBuilder.and(hashPredicate, numericPredicate); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java index 6077495a1c2..51082ac8784 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java @@ -20,15 +20,38 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IDao; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.util.SourceParam; +import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -41,6 +64,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -51,16 +76,32 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.persistence.criteria.*; -import java.util.*; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; @Component @Scope("prototype") class PredicateBuilderReference extends BasePredicateBuilder { private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderReference.class); - + private final PredicateBuilder myPredicateBuilder; @Autowired IdHelperService myIdHelperService; @Autowired @@ -69,8 +110,10 @@ class PredicateBuilderReference extends BasePredicateBuilder { MatchUrlService myMatchUrlService; @Autowired DaoRegistry myDaoRegistry; - - private final PredicateBuilder myPredicateBuilder; + @Autowired + PartitionSettings myPartitionSettings; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; PredicateBuilderReference(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) { super(theSearchBuilder); @@ -85,8 +128,10 @@ class PredicateBuilderReference extends BasePredicateBuilder { String theParamName, List theList, SearchFilterParser.CompareOperation operation, - RequestDetails theRequest) { + RequestDetails theRequest, + RequestPartitionId theRequestPartitionId) { + //Is this just to ensure the chain has been split correctly??? assert theParamName.contains(".") == false; if ((operation != null) && @@ -96,7 +141,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { } if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing()); + addPredicateParamMissingForReference(theResourceName, theParamName, theList.get(0).getMissing(), theRequestPartitionId); return null; } @@ -136,7 +181,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { * Handle chained search, e.g. Patient?organization.name=Kwik-e-mart */ - return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest); + return addPredicateReferenceWithChain(theResourceName, theParamName, theList, join, new ArrayList<>(), ref, theRequest, theRequestPartitionId); } @@ -147,9 +192,16 @@ class PredicateBuilderReference extends BasePredicateBuilder { } List codePredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + + for (IIdType next : targetIds) { + if (!next.hasResourceType()) { + warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, null); + } + } // Resources by ID - List targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest); + List targetPids = myIdHelperService.resolveResourcePersistentIdsWithCache(theRequestPartitionId, targetIds, theRequest); if (!targetPids.isEmpty()) { ourLog.debug("Searching for resource link with target PIDs: {}", targetPids); Predicate pathPredicate; @@ -160,11 +212,15 @@ class PredicateBuilderReference extends BasePredicateBuilder { } Predicate pidPredicate; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { - pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)); + if (targetPids.size() == 1) { + pidPredicate = myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), targetPids.get(0).getIdAsLong()); + } else { + pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)); + } } else { pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)).not(); } - codePredicates.add(myBuilder.and(pathPredicate, pidPredicate)); + codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate)); } // Resources by fully qualified URL @@ -182,11 +238,12 @@ class PredicateBuilderReference extends BasePredicateBuilder { } else { pidPredicate = join.get("myTargetResourceUrl").in(targetQualifiedUrls).not(); } - codePredicates.add(myBuilder.and(pathPredicate, pidPredicate)); + codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate)); } + myQueryRoot.setHasIndexJoins(); if (codePredicates.size() > 0) { - Predicate predicate = myBuilder.or(toArray(codePredicates)); + Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates)); myQueryRoot.addPredicate(predicate); return predicate; } else { @@ -198,9 +255,13 @@ class PredicateBuilderReference extends BasePredicateBuilder { } } - private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List theList, Join theJoin, List theCodePredicates, ReferenceParam theRef, RequestDetails theRequest) { + /** + * This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain + * on the device. + */ + private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List theList, Join theJoin, List theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { final List> resourceTypes; - if (!theRef.hasResourceType()) { + if (!theReferenceParam.hasResourceType()) { RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); resourceTypes = new ArrayList<>(); @@ -258,20 +319,45 @@ class PredicateBuilderReference extends BasePredicateBuilder { } } else { + try { - RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theRef.getResourceType()); + RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType()); resourceTypes = new ArrayList<>(1); resourceTypes.add(resDef.getImplementingClass()); } catch (DataFormatException e) { - throw new InvalidRequestException("Invalid resource type: " + theRef.getResourceType()); + throw newInvalidResourceTypeException(theReferenceParam.getResourceType()); } + + } + + // Handle chain on _type + if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) { + String typeValue = theReferenceParam.getValue(); + + Class wantedType; + try { + wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass(); + } catch (DataFormatException e) { + throw newInvalidResourceTypeException(typeValue); + } + if (!resourceTypes.contains(wantedType)) { + throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue); + } + + Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin); + Predicate sourceTypeParameter = myCriteriaBuilder.equal(theJoin.get("mySourceResourceType"), myResourceName); + Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue); + + Predicate composite = myCriteriaBuilder.and(pathPredicate, sourceTypeParameter, targetTypeParameter); + myQueryRoot.addPredicate(composite); + return composite; } boolean foundChainMatch = false; - + List> candidateTargetTypes = new ArrayList<>(); for (Class nextType : resourceTypes) { + String chain = theReferenceParam.getChain(); - String chain = theRef.getChain(); String remainingChain = null; int chainDotIndex = chain.indexOf('.'); if (chainDotIndex != -1) { @@ -317,31 +403,55 @@ class PredicateBuilderReference extends BasePredicateBuilder { orValues.add(chainValue); } - Subquery subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest); + + Subquery subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest, theRequestPartitionId); Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin); Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ); - Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate); + Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, pidPredicate); theCodePredicates.add(andPredicate); - + candidateTargetTypes.add(nextType); } if (!foundChainMatch) { - throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theRef.getChain())); + throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain())); } - Predicate predicate = myBuilder.or(toArray(theCodePredicates)); + if (candidateTargetTypes.size() > 1) { + warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes); + } + + Predicate predicate = myCriteriaBuilder.or(toArray(theCodePredicates)); myQueryRoot.addPredicate(predicate); return predicate; } - Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From from) { - return createResourceLinkPathPredicate(myContext, theParamName, from, theResourceName); + private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, @Nullable List> theCandidateTargetTypes) { + StringBuilder builder = new StringBuilder(); + builder.append("This search uses an unqualified resource(a parameter in a chain without a resource type). "); + builder.append("This is less efficient than using a qualified type. "); + if (theCandidateTargetTypes != null) { + builder.append("[" + theParamName + "] resolves to [" + theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) + "]."); + builder.append("If you know what you're looking for, try qualifying it using the form "); + builder.append(theCandidateTargetTypes.stream().map(cls -> "[" + cls.getSimpleName() + ":" + theParamName + "]").collect(Collectors.joining(" or "))); + } else { + builder.append("If you know what you're looking for, try qualifying it using the form: '"); + builder.append(theParamName).append(":[resourceType]"); + builder.append("'"); + } + String message = builder + .toString(); + StorageProcessingMessage msg = new StorageProcessingMessage() + .setMessage(message); + HookParams params = new HookParams() + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest) + .add(StorageProcessingMessage.class, msg); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); } - private Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, From theFrom, - String theResourceType) { - RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType); + Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From from) { + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName); RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName); List path = param.getPathsSplit(); @@ -354,12 +464,18 @@ class PredicateBuilderReference extends BasePredicateBuilder { ListIterator iter = path.listIterator(); while (iter.hasNext()) { String nextPath = trim(iter.next()); - if (!nextPath.contains(theResourceType + ".")) { + if (!nextPath.contains(theResourceName + ".")) { iter.remove(); } } - return theFrom.get("mySourcePath").in(path); + // one value + if (path.size() == 1) { + return myCriteriaBuilder.equal(from.get("mySourcePath").as(String.class), path.get(0)); + } + + // multiple values + return from.get("mySourcePath").in(path); } private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, Class nextType, String chain, boolean isMeta, String resourceId) { @@ -384,7 +500,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { return chainValue; } - Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest) { + Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { Subquery subQ = myQueryRoot.subquery(Long.class); /* * We're doing a chain call, so push the current query root @@ -398,11 +514,11 @@ class PredicateBuilderReference extends BasePredicateBuilder { andOrParams.add(theOrValues); // Create the subquery predicates - myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName)); - myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted"))); + myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName)); + myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted"))); if (theFoundChainMatch) { - searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest); + searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, theRequestPartitionId); subQ.where(myQueryRoot.getPredicateArray()); } @@ -413,7 +529,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { return subQ; } - void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest) { + void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { if (theAndOrParams.isEmpty()) { return; @@ -421,7 +537,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { switch (theParamName) { case IAnyResource.SP_RES_ID: - myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequest); + myPredicateBuilder.addPredicateResourceId(theAndOrParams, theResourceName, theRequestPartitionId); break; case IAnyResource.SP_RES_LANGUAGE: @@ -430,13 +546,13 @@ class PredicateBuilderReference extends BasePredicateBuilder { break; case Constants.PARAM_HAS: - addPredicateHas(theAndOrParams, theRequest); + addPredicateHas(theResourceName, theAndOrParams, theRequest, theRequestPartitionId); break; case Constants.PARAM_TAG: case Constants.PARAM_PROFILE: case Constants.PARAM_SECURITY: - myPredicateBuilder.addPredicateTag(theAndOrParams, theParamName); + myPredicateBuilder.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId); break; case Constants.PARAM_SOURCE: @@ -447,56 +563,63 @@ class PredicateBuilderReference extends BasePredicateBuilder { RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); if (nextParamDef != null) { + + if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.isIncludePartitionInSearchHashes()) { + if (theRequestPartitionId == null) { + throw new PreconditionFailedException("This server is not configured to support search against all partitions"); + } + } + switch (nextParamDef.getParamType()) { case DATE: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateDate(theResourceName, theParamName, nextAnd, null); + myPredicateBuilder.addPredicateDate(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); } break; case QUANTITY: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateQuantity(theResourceName, theParamName, nextAnd, null); + myPredicateBuilder.addPredicateQuantity(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); } break; case REFERENCE: for (List nextAnd : theAndOrParams) { - addPredicate(theResourceName, theParamName, nextAnd, null, theRequest); + addPredicate(theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId); } break; case STRING: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateString(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.sw); + myPredicateBuilder.addPredicateString(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); } break; case TOKEN: for (List nextAnd : theAndOrParams) { if ("Location.position".equals(nextParamDef.getPath())) { - myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd); + myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, theRequestPartitionId); } else { - myPredicateBuilder.addPredicateToken(theResourceName, theParamName, nextAnd, null); + myPredicateBuilder.addPredicateToken(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); } } break; case NUMBER: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateNumber(theResourceName, theParamName, nextAnd, null); + myPredicateBuilder.addPredicateNumber(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); } break; case COMPOSITE: for (List nextAnd : theAndOrParams) { - addPredicateComposite(theResourceName, nextParamDef, nextAnd); + addPredicateComposite(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); } break; case URI: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateUri(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.eq); + myPredicateBuilder.addPredicateUri(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.eq, theRequestPartitionId); } break; case HAS: case SPECIAL: for (List nextAnd : theAndOrParams) { if ("Location.position".equals(nextParamDef.getPath())) { - myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd); + myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, theRequestPartitionId); } } break; @@ -525,10 +648,16 @@ class PredicateBuilderReference extends BasePredicateBuilder { // point to do something more fancy... ArrayList holdPredicates = new ArrayList<>(myQueryRoot.getPredicates()); - Predicate filterPredicate = processFilter(filter, theResourceName, theRequest); + Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, theRequestPartitionId); myQueryRoot.clearPredicates(); myQueryRoot.addPredicates(holdPredicates); myQueryRoot.addPredicate(filterPredicate); + + // Because filters can have an OR at the root, we never know for sure that we haven't done an optimized + // search that doesn't check the resource type. This could be improved in the future, but for now it's + // safest to just clear this flag. The test "testRetrieveDifferentTypeEq" will fail if we don't clear + // this here. + myQueryRoot.clearHasIndexJoins(); } } @@ -541,35 +670,30 @@ class PredicateBuilderReference extends BasePredicateBuilder { } } - private Predicate processFilter(SearchFilterParser.Filter theFilter, - String theResourceName, RequestDetails theRequest) { + private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { if (theFilter instanceof SearchFilterParser.FilterParameter) { - return processFilterParameter((SearchFilterParser.FilterParameter) theFilter, - theResourceName, theRequest); + return processFilterParameter((SearchFilterParser.FilterParameter) theFilter, theResourceName, theRequest, theRequestPartitionId); } else if (theFilter instanceof SearchFilterParser.FilterLogical) { // Left side - Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), - theResourceName, theRequest); + Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest, theRequestPartitionId); // Right side - Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), - theResourceName, theRequest); + Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest, theRequestPartitionId); if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { - return myBuilder.and(xPredicate, yPredicate); + return myCriteriaBuilder.and(xPredicate, yPredicate); } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { - return myBuilder.or(xPredicate, yPredicate); + return myCriteriaBuilder.or(xPredicate, yPredicate); } } else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) { - return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), - theResourceName, theRequest); + return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest, theRequestPartitionId); } return null; } private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter, - String theResourceName, RequestDetails theRequest) { + String theResourceName, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName()); @@ -582,7 +706,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { null, null, theFilter.getValue()); - return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequest); + return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequestPartitionId); } else { throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search"); } @@ -604,13 +728,13 @@ class PredicateBuilderReference extends BasePredicateBuilder { } else { RestSearchParameterTypeEnum typeEnum = searchParam.getParamType(); if (typeEnum == RestSearchParameterTypeEnum.URI) { - return myPredicateBuilder.addPredicateUri(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation()); + return myPredicateBuilder.addPredicateUri(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.STRING) { - return myPredicateBuilder.addPredicateString(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation()); + return myPredicateBuilder.addPredicateString(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.DATE) { - return myPredicateBuilder.addPredicateDate(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation()); + return myPredicateBuilder.addPredicateDate(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) { - return myPredicateBuilder.addPredicateNumber(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation()); + return myPredicateBuilder.addPredicateNumber(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) { String paramName = theFilter.getParamPath().getName(); SearchFilterParser.CompareOperation operation = theFilter.getOperation(); @@ -618,9 +742,9 @@ class PredicateBuilderReference extends BasePredicateBuilder { String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null; String value = theFilter.getValue(); ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value); - return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest); + return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) { - return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation()); + return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) { throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses"); } else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) { @@ -629,7 +753,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { null, null, theFilter.getValue()); - return myPredicateBuilder.addPredicateToken(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(param), theFilter.getOperation()); + return myPredicateBuilder.addPredicateToken(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId); } } return null; @@ -666,6 +790,11 @@ class PredicateBuilderReference extends BasePredicateBuilder { qp = new ReferenceParam(); break; case SPECIAL: + if ("Location.position".equals(theParam.getPath())) { + qp = new SpecialParam(); + break; + } + throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType()); case URI: case HAS: default: @@ -702,7 +831,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { continue; } - Predicate predicate = null; + Predicate predicate; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { predicate = myQueryRoot.get("myLanguage").as(String.class).in(values); @@ -727,6 +856,9 @@ class PredicateBuilderReference extends BasePredicateBuilder { } private Predicate addPredicateSource(List theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { + // Required for now + assert theOperation == SearchFilterParser.CompareOperation.eq; + if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) { String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled"); throw new InvalidRequestException(msg); @@ -740,10 +872,10 @@ class PredicateBuilderReference extends BasePredicateBuilder { SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext)); String sourceUri = sourceParameter.getSourceUri(); String requestId = sourceParameter.getRequestId(); - Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri); - Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId); + Predicate sourceUriPredicate = myCriteriaBuilder.equal(join.get("mySourceUri"), sourceUri); + Predicate requestIdPredicate = myCriteriaBuilder.equal(join.get("myRequestId"), requestId); if (isNotBlank(sourceUri) && isNotBlank(requestId)) { - codePredicates.add(myBuilder.and(sourceUriPredicate, requestIdPredicate)); + codePredicates.add(myCriteriaBuilder.and(sourceUriPredicate, requestIdPredicate)); } else if (isNotBlank(sourceUri)) { codePredicates.add(sourceUriPredicate); } else if (isNotBlank(requestId)) { @@ -751,12 +883,12 @@ class PredicateBuilderReference extends BasePredicateBuilder { } } - Predicate retVal = myBuilder.or(toArray(codePredicates)); + Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); myQueryRoot.addPredicate(retVal); return retVal; } - private void addPredicateHas(List> theHasParameters, RequestDetails theRequest) { + private void addPredicateHas(String theResourceType, List> theHasParameters, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { for (List nextOrList : theHasParameters) { @@ -786,12 +918,16 @@ class PredicateBuilderReference extends BasePredicateBuilder { throw new InvalidRequestException("Invalid resource type: " + targetResourceType); } - assert parameterName != null; + //Ensure that the name of the search param + // (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val) + // exists on the target resource type. RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName); if (owningParameterDef == null) { throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); } + //Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val) + //exists on the target resource. owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference); if (owningParameterDef == null) { throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference); @@ -806,18 +942,31 @@ class PredicateBuilderReference extends BasePredicateBuilder { for (IQueryParameterOr next : parsedParam.getValuesAsQueryTokens()) { orValues.addAll(next.getValuesAsQueryTokens()); } + //Handle internal chain inside the has. + if (parameterName.contains(".")) { + String chainedPartOfParameter = getChainedPart(parameterName); + orValues.stream() + .filter(qp -> qp instanceof ReferenceParam) + .map(qp -> (ReferenceParam) qp) + .forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter))); + } - Subquery subQ = myPredicateBuilder.createLinkSubquery(parameterName, targetResourceType, orValues, theRequest); - + Subquery subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, theRequestPartitionId); Join join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT); + Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join); - Predicate pidPredicate = join.get("mySourceResourcePid").in(subQ); - Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate); + Predicate sourceTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType"), theResourceType); + Predicate sourcePidPredicate = join.get("mySourceResourcePid").in(subQ); + Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, sourcePidPredicate, sourceTypePredicate); myQueryRoot.addPredicate(andPredicate); } } - private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List theNextAnd) { + private String getChainedPart(String parameter) { + return parameter.substring(parameter.indexOf(".") + 1); + } + + private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List theNextAnd, RequestPartitionId theRequestPartitionId) { // TODO: fail if missing is set for a composite query IQueryParameterType or = theNextAnd.get(0); @@ -828,37 +977,37 @@ class PredicateBuilderReference extends BasePredicateBuilder { RuntimeSearchParam left = theParamDef.getCompositeOf().get(0); IQueryParameterType leftValue = cp.getLeftValue(); - myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue)); + myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue, theRequestPartitionId)); RuntimeSearchParam right = theParamDef.getCompositeOf().get(1); IQueryParameterType rightValue = cp.getRightValue(); - myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue)); + myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue, theRequestPartitionId)); } - private Predicate createCompositeParamPart(String theResourceName, Root theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) { + private Predicate createCompositeParamPart(String theResourceName, Root theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue, RequestPartitionId theRequestPartitionId) { Predicate retVal = null; switch (theParam.getParamType()) { case STRING: { From stringJoin = theRoot.join("myParamsString", JoinType.INNER); - retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin); + retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, stringJoin, theRequestPartitionId); break; } case TOKEN: { From tokenJoin = theRoot.join("myParamsToken", JoinType.INNER); List tokens = Collections.singletonList(leftValue); - Collection tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myBuilder, tokenJoin); - retVal = myBuilder.and(tokenPredicates.toArray(new Predicate[0])); + Collection tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myCriteriaBuilder, tokenJoin, theRequestPartitionId); + retVal = myCriteriaBuilder.and(tokenPredicates.toArray(new Predicate[0])); break; } case DATE: { From dateJoin = theRoot.join("myParamsDate", JoinType.INNER); - retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin); + retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, theRequestPartitionId); break; } case QUANTITY: { From dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER); - retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin); + retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin, theRequestPartitionId); break; } case COMPOSITE: @@ -876,4 +1025,18 @@ class PredicateBuilderReference extends BasePredicateBuilder { return retVal; } + + @Nonnull + private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) { + String searchParamName = theResourceName + ":" + theParamName; + String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName); + return new InvalidRequestException(msg); + } + + @Nonnull + private InvalidRequestException newInvalidResourceTypeException(String theResourceType) { + String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType); + throw new InvalidRequestException(msg); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java index 89e90bdb447..021e1668b81 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java @@ -20,12 +20,12 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.r4.model.IdType; import org.slf4j.Logger; @@ -58,9 +58,9 @@ class PredicateBuilderResourceId extends BasePredicateBuilder { } @Nullable - Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { + Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, theRequest); + Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, theRequestPartitionId); if (nextPredicate != null) { myQueryRoot.addPredicate(nextPredicate); @@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder { } @Nullable - private Predicate createPredicate(Root theRoot, String theResourceName, List> theValues, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { + private Predicate createPredicate(Root theRoot, String theResourceName, List> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { Predicate nextPredicate = null; Set allOrPids = null; @@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder { if (isNotBlank(value)) { haveValue = true; try { - ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(theResourceName, valueAsId.getIdPart(), theRequest); + ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart()); orPids.add(pid); } catch (ResourceNotFoundException e) { // This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest @@ -110,7 +110,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder { if (allOrPids != null && allOrPids.isEmpty()) { // This will never match - nextPredicate = myBuilder.equal(theRoot.get("myId").as(Long.class), -1); + nextPredicate = myCriteriaBuilder.equal(theRoot.get("myId").as(Long.class), -1); } else if (allOrPids != null) { @@ -121,13 +121,11 @@ class PredicateBuilderResourceId extends BasePredicateBuilder { default: case eq: codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids))); - codePredicates.add(myBuilder.equal(myQueryRoot.get("myResourceType"), theResourceName)); - nextPredicate = myBuilder.and(toArray(codePredicates)); + nextPredicate = myCriteriaBuilder.and(toArray(codePredicates)); break; case ne: codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)).not()); - codePredicates.add(myBuilder.equal(myQueryRoot.get("myResourceType"), theResourceName)); - nextPredicate = myBuilder.and(toArray(codePredicates)); + nextPredicate = myCriteriaBuilder.and(toArray(codePredicates)); break; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java index 3f770fbdfef..7bbfa5c6c8e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java @@ -20,7 +20,8 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -56,29 +57,29 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation theOperation, + RequestPartitionId theRequestPartitionId) { Join join = createJoin(SearchBuilderJoinEnum.STRING, theParamName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } List codePredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + for (IQueryParameterType nextOr : theList) { - IQueryParameterType theParameter = nextOr; - Predicate singleCode = createPredicateString(theParameter, - theResourceName, - theParamName, - myBuilder, - join, - operation); + Predicate singleCode = createPredicateString(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); codePredicates.add(singleCode); } - Predicate retVal = myBuilder.or(toArray(codePredicates)); + Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); + + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(retVal); + return retVal; } @@ -86,13 +87,15 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + From theFrom, + RequestPartitionId theRequestPartitionId) { return createPredicateString(theParameter, theResourceName, theParamName, theBuilder, theFrom, - null); + null, + theRequestPartitionId); } private Predicate createPredicateString(IQueryParameterType theParameter, @@ -100,7 +103,8 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB String theParamName, CriteriaBuilder theBuilder, From theFrom, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { String rawSearchTerm; if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; @@ -150,12 +154,12 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB singleCode = theBuilder.and(singleCode, exactCode); } - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact(); if (exactMatch) { // Exact match - Long hash = ResourceIndexedSearchParamString.calculateHashExact(theResourceName, theParamName, rawSearchTerm); + Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, rawSearchTerm); return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash); } else { // Normalized Match @@ -183,34 +187,34 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB Predicate predicate; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.sw)) { - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); + Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); predicate = theBuilder.and(hashCode, singleCode); } else if ((operation == SearchFilterParser.CompareOperation.ew) || (operation == SearchFilterParser.CompareOperation.co)) { Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.eq) { - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); + Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString); predicate = theBuilder.and(hashCode, singleCode); } else if (operation == SearchFilterParser.CompareOperation.ne) { Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.gt) { Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.lt) { Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.ge) { Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.le) { Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); } else { throw new IllegalArgumentException("Don't yet know how to handle operation " + operation + " on a string"); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java index a52237682f6..04bab4248ea 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTag; @@ -38,7 +39,14 @@ import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; import java.util.List; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -52,7 +60,7 @@ class PredicateBuilderTag extends BasePredicateBuilder { super(theSearchBuilder); } - void addPredicateTag(List> theList, String theParamName) { + void addPredicateTag(List> theList, String theParamName, RequestPartitionId theRequestPartitionId) { TagTypeEnum tagType; if (Constants.PARAM_TAG.equals(theParamName)) { tagType = TagTypeEnum.TAG; @@ -134,8 +142,8 @@ class PredicateBuilderTag extends BasePredicateBuilder { subQ.select(subQfrom.get("myResourceId").as(Long.class)); myQueryRoot.addPredicate( - myBuilder.not( - myBuilder.in( + myCriteriaBuilder.not( + myCriteriaBuilder.in( myQueryRoot.get("myId") ).value(subQ) ) @@ -147,17 +155,28 @@ class PredicateBuilderTag extends BasePredicateBuilder { subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin)); - Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens); + Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myCriteriaBuilder, tagType, tokens); defJoin.where(tagListPredicate); continue; + + } else { + + myQueryRoot.setHasIndexJoins(); + } Join tagJoin = myQueryRoot.join("myTags", JoinType.LEFT); From defJoin = tagJoin.join("myTag"); - Predicate tagListPredicate = createPredicateTagList(defJoin, myBuilder, tagType, tokens); - myQueryRoot.addPredicate(tagListPredicate); + Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens); + List predicates = Lists.newArrayList(tagListPredicate); + + if (theRequestPartitionId != null) { + addPartitionIdPredicate(theRequestPartitionId, tagJoin, predicates); + } + + myQueryRoot.addPredicates(predicates); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java index 7087cd3c02d..b2f62941c5f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java @@ -24,12 +24,13 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.base.composite.BaseCodingDt; @@ -38,6 +39,7 @@ import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.VersionIndependentConcept; import com.google.common.collect.Sets; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPredicate; @@ -45,20 +47,31 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.persistence.criteria.*; -import java.util.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; @Component @Scope("prototype") class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBuilder { + private final PredicateBuilder myPredicateBuilder; @Autowired private ITermReadSvc myTerminologySvc; @Autowired private ISearchParamRegistry mySearchParamRegistry; - private final PredicateBuilder myPredicateBuilder; PredicateBuilderToken(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) { super(theSearchBuilder); @@ -69,22 +82,24 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation theOperation, + RequestPartitionId theRequestPartitionId) { if (theList.get(0).getMissing() != null) { Join join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName); - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } List codePredicates = new ArrayList<>(); + List tokens = new ArrayList<>(); for (IQueryParameterType nextOr : theList) { if (nextOr instanceof TokenParam) { TokenParam id = (TokenParam) nextOr; if (id.isText()) { - myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList); + myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList, theRequestPartitionId); break; } } @@ -97,12 +112,17 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu } Join join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName); - Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myBuilder, join, operation); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + + Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); assert singleCode != null; codePredicates.addAll(singleCode); - Predicate spPredicate = myBuilder.or(toArray(codePredicates)); + Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates)); + + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(spPredicate); + return spPredicate; } @@ -110,14 +130,16 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu String theResourceName, String theParamName, CriteriaBuilder theBuilder, - From theFrom) { + From theFrom, + RequestPartitionId theRequestPartitionId) { return createPredicateToken( theParameters, theResourceName, theParamName, theBuilder, theFrom, - null); + null, + theRequestPartitionId); } private Collection createPredicateToken(Collection theParameters, @@ -125,7 +147,8 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu String theParamName, CriteriaBuilder theBuilder, From theFrom, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { final List codes = new ArrayList<>(); TokenParamModifier modifier = null; @@ -169,7 +192,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu */ if (modifier == TokenParamModifier.IN) { - codes.addAll(myTerminologySvc.expandValueSet(code)); + codes.addAll(myTerminologySvc.expandValueSet(null, code)); } else if (modifier == TokenParamModifier.ABOVE) { system = determineSystemIfMissing(theParamName, code, system); validateHaveSystemAndCodeForToken(theParamName, code, system); @@ -201,19 +224,19 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu // System only List systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList()); if (!systemOnlyCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY)); + retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, theRequestPartitionId)); } // Code only List codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList()); if (!codeOnlyCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY)); + retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, theRequestPartitionId)); } // System and code List systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList()); if (!systemAndCodeCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE)); + retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, theRequestPartitionId)); } return retVal; @@ -237,7 +260,9 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu } if (valueSetUris.size() == 1) { String valueSet = valueSetUris.iterator().next(); - List candidateCodes = myTerminologySvc.expandValueSet(valueSet); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setFailOnMissingCodeSystem(false); + List candidateCodes = myTerminologySvc.expandValueSet(options, valueSet); for (VersionIndependentConcept nextCandidate : candidateCodes) { if (nextCandidate.getCode().equals(code)) { retVal = nextCandidate.getSystem(); @@ -263,7 +288,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu } } - private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From theFrom, List theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode) { + private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From theFrom, List theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode, RequestPartitionId theRequestPartitionId) { if (myDontUseHashesForSearch) { final Path systemExpression = theFrom.get("mySystem"); final Path valueExpression = theFrom.get("myValue"); @@ -296,7 +321,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu or = theBuilder.not(or); } - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or, theRequestPartitionId); } /* @@ -311,14 +336,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu hashField = theFrom.get("myHashSystem").as(Long.class); values = theTokens .stream() - .map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(theResourceName, theParamName, t.getSystem())) + .map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getSystem())) .collect(Collectors.toList()); break; case VALUE_ONLY: hashField = theFrom.get("myHashValue").as(Long.class); values = theTokens .stream() - .map(t -> ResourceIndexedSearchParamToken.calculateHashValue(theResourceName, theParamName, t.getCode())) + .map(t -> ResourceIndexedSearchParamToken.calculateHashValue(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getCode())) .collect(Collectors.toList()); break; case SYSTEM_AND_VALUE: @@ -326,14 +351,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu hashField = theFrom.get("myHashSystemAndValue").as(Long.class); values = theTokens .stream() - .map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(theResourceName, theParamName, t.getSystem(), t.getCode())) + .map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getSystem(), t.getCode())) .collect(Collectors.toList()); break; } Predicate predicate = hashField.in(values); if (theModifier == TokenParamModifier.NOT) { - Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName)); + Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName)); Predicate disjunctionPredicate = theBuilder.not(predicate); predicate = theBuilder.and(identityPredicate, disjunctionPredicate); } @@ -342,8 +367,8 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu private Expression toEqualOrIsNullPredicate(Path theExpression, T theCode) { if (theCode == null) { - return myBuilder.isNull(theExpression); + return myCriteriaBuilder.isNull(theExpression); } - return myBuilder.equal(theExpression, theCode); + return myCriteriaBuilder.equal(theExpression, theCode); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java index 1011759e1bb..45488ccf759 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; @@ -55,16 +56,19 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil public Predicate addPredicate(String theResourceName, String theParamName, List theList, - SearchFilterParser.CompareOperation operation) { + SearchFilterParser.CompareOperation operation, + RequestPartitionId theRequestPartitionId) { Join join = createJoin(SearchBuilderJoinEnum.URI, theParamName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join); + addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } List codePredicates = new ArrayList<>(); + addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); + for (IQueryParameterType nextOr : theList) { if (nextOr instanceof UriParam) { @@ -93,7 +97,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil List toFind = new ArrayList<>(); for (String next : candidates) { if (value.length() >= next.length()) { - if (value.substring(0, next.length()).equals(next)) { + if (value.startsWith(next)) { toFind.add(next); } } @@ -104,51 +108,51 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil } Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind); - Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate); + Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, theRequestPartitionId); codePredicates.add(hashAndUriPredicate); } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) { - Predicate uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); - Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate); + Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); + Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, theRequestPartitionId); codePredicates.add(hashAndUriPredicate); } else { if (myDontUseHashesForSearch) { - Predicate predicate = myBuilder.equal(join.get("myUri").as(String.class), value); + Predicate predicate = myCriteriaBuilder.equal(join.get("myUri").as(String.class), value); codePredicates.add(predicate); } else { Predicate uriPredicate = null; if (operation == null || operation == SearchFilterParser.CompareOperation.eq) { - long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value); - Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri); + long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, value); + Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri); codePredicates.add(hashPredicate); } else if (operation == SearchFilterParser.CompareOperation.ne) { - uriPredicate = myBuilder.notEqual(join.get("myUri").as(String.class), value); + uriPredicate = myCriteriaBuilder.notEqual(join.get("myUri").as(String.class), value); } else if (operation == SearchFilterParser.CompareOperation.co) { - uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value)); + uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value)); } else if (operation == SearchFilterParser.CompareOperation.gt) { - uriPredicate = myBuilder.greaterThan(join.get("myUri").as(String.class), value); + uriPredicate = myCriteriaBuilder.greaterThan(join.get("myUri").as(String.class), value); } else if (operation == SearchFilterParser.CompareOperation.lt) { - uriPredicate = myBuilder.lessThan(join.get("myUri").as(String.class), value); + uriPredicate = myCriteriaBuilder.lessThan(join.get("myUri").as(String.class), value); } else if (operation == SearchFilterParser.CompareOperation.ge) { - uriPredicate = myBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value); + uriPredicate = myCriteriaBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value); } else if (operation == SearchFilterParser.CompareOperation.le) { - uriPredicate = myBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value); + uriPredicate = myCriteriaBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value); } else if (operation == SearchFilterParser.CompareOperation.sw) { - uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); + uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); } else if (operation == SearchFilterParser.CompareOperation.ew) { - uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value)); + uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value)); } else { throw new IllegalArgumentException(String.format("Unsupported operator specified in _filter clause, %s", operation.toString())); } if (uriPredicate != null) { - long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName); - Predicate hashIdentityPredicate = myBuilder.equal(join.get("myHashIdentity"), hashIdentity); - codePredicates.add(myBuilder.and(hashIdentityPredicate, uriPredicate)); + long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName); + Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity); + codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate)); } } } @@ -164,18 +168,22 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil * just add a predicate that can never match */ if (codePredicates.isEmpty()) { - Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class)); + Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class)); + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(predicate); return null; } - Predicate orPredicate = myBuilder.or(toArray(codePredicates)); + Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates)); Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, - orPredicate); + orPredicate, + theRequestPartitionId); + myQueryRoot.setHasIndexJoins(); myQueryRoot.addPredicate(outerPredicate); return outerPredicate; } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java index 3eeeed71efe..5afdbcb5a6d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java @@ -96,7 +96,11 @@ public class QueryRoot { return myHasIndexJoins; } - public void setHasIndexJoins(boolean theHasIndexJoins) { + public void setHasIndexJoins() { myHasIndexJoins = true; } + + public void clearHasIndexJoins() { + myHasIndexJoins = false; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaValidationSupport.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaValidationSupport.java deleted file mode 100644 index 07e0cafaa93..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaValidationSupport.java +++ /dev/null @@ -1,138 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -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.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.UriParam; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.annotation.PostConstruct; - -public abstract class BaseJpaValidationSupport { - - private static final Logger ourLog = LoggerFactory.getLogger(BaseJpaValidationSupport.class); - - @Autowired - private FhirContext myR4Ctx; - @Autowired - private DaoRegistry myDaoRegistry; - private IFhirResourceDao myStructureDefinitionDao; - private IFhirResourceDao myValueSetDao; - private IFhirResourceDao myQuestionnaireDao; - private IFhirResourceDao myCodeSystemDao; - private IFhirResourceDao myImplementationGuideDao; - - @SuppressWarnings({"unchecked", "unused"}) - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - IdType id = new IdType(theUri); - boolean localReference = false; - if (id.hasBaseUrl() == false && id.hasIdPart() == true) { - localReference = true; - } - - String resourceName = myR4Ctx.getResourceDefinition(theClass).getName(); - IBundleProvider search; - if ("ValueSet".equals(resourceName)) { - if (localReference) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(IAnyResource.SP_RES_ID, new StringParam(theUri)); - search = myValueSetDao.search(params); - if (search.size() == 0) { - params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(ValueSet.SP_URL, new UriParam(theUri)); - search = myValueSetDao.search(params); - } - } else { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(ValueSet.SP_URL, new UriParam(theUri)); - search = myValueSetDao.search(params); - } - } else if ("StructureDefinition".equals(resourceName)) { - // Don't allow the core FHIR definitions to be overwritten - if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { - String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); - if (myR4Ctx.getElementDefinition(typeName) != null) { - return null; - } - } - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(StructureDefinition.SP_URL, new UriParam(theUri)); - search = myStructureDefinitionDao.search(params); - } else if ("Questionnaire".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - if (localReference) { - params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart())); - } else { - params.add(Questionnaire.SP_URL, new UriParam(id.getValue())); - } - search = myQuestionnaireDao.search(params); - } else if ("CodeSystem".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(CodeSystem.SP_URL, new UriParam(theUri)); - search = myCodeSystemDao.search(params); - } else if ("ImplementationGuide".equals(resourceName)) { - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(1); - params.add(ImplementationGuide.SP_URL, new UriParam(theUri)); - search = myImplementationGuideDao.search(params); - } else { - throw new IllegalArgumentException("Can't fetch resource type: " + resourceName); - } - - Integer size = search.size(); - if (size == null || size == 0) { - return null; - } - - if (size > 1) { - ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri); - } - - return (T) search.getResources(0, 1).get(0); - } - - @PostConstruct - public void start() { - myStructureDefinitionDao = myDaoRegistry.getResourceDao("StructureDefinition"); - myValueSetDao = myDaoRegistry.getResourceDao("ValueSet"); - myQuestionnaireDao = myDaoRegistry.getResourceDao("Questionnaire"); - myCodeSystemDao = myDaoRegistry.getResourceDao("CodeSystem"); - myImplementationGuideDao = myDaoRegistry.getResourceDao("ImplementationGuide"); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index 1dca20045b5..61e856d90bf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -21,13 +21,13 @@ package ca.uhn.fhir.jpa.dao.r4; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; @@ -38,7 +38,6 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -59,7 +58,7 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -103,10 +102,10 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao implements IFhirResourceDaoEncounter { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java index af886f62042..82733eaac73 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoMessageHeader; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader; import org.hl7.fhir.r4.model.MessageHeader; public class FhirResourceDaoMessageHeaderR4 extends BaseHapiFhirResourceDao implements IFhirResourceDaoMessageHeader { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java index 85ca4d0fd5c..1978a34be66 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java @@ -20,24 +20,25 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; - +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Patient; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; public class FhirResourceDaoPatientR4 extends BaseHapiFhirResourceDaoimplements IFhirResourceDaoPatient { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java index fdb867824e2..91d26e45695 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java @@ -2,24 +2,22 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ElementUtil; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.utils.FHIRLexer; -import org.hl7.fhir.r4.utils.FHIRPathEngine; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.SearchParameter; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; @@ -48,9 +46,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - public static final DefaultProfileValidationSupport VALIDATION_SUPPORT = new DefaultProfileValidationSupport(); - @Autowired - private IFhirSystemDao mySystemDao; @Autowired private ISearchParamExtractor mySearchParamExtractor; @@ -85,14 +80,15 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao status = theResource.getStatus(); List base = theResource.getBase(); + String code = theResource.getCode(); String expression = theResource.getExpression(); FhirContext context = getContext(); Enum type = theResource.getType(); - FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); + FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig()); } - public static void validateSearchParam(ISearchParamExtractor theSearchParamExtractor, Enum theType, Enum theStatus, List theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) { + public static void validateSearchParam(ISearchParamRegistry theSearchParamRegistry, ISearchParamExtractor theSearchParamExtractor, String theCode, Enum theType, Enum theStatus, List theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) { if (theStatus == null) { throw new UnprocessableEntityException("SearchParameter.status is missing or invalid"); } @@ -153,6 +149,19 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao nextBaseType : theBase) { + String nextBase = nextBaseType.getValueAsString(); + RuntimeSearchParam existingSearchParam = theSearchParamRegistry.getActiveSearchParam(nextBase, theCode); + if (existingSearchParam != null && existingSearchParam.getId() == null) { + throw new UnprocessableEntityException("Can not override built-in search parameter " + nextBase + ":" + theCode + " because overriding is disabled on this server"); + } + } + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoStructureDefinitionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoStructureDefinitionR4.java index 69525c1c824..3fc4aec2c79 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoStructureDefinitionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoStructureDefinitionR4.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.StructureDefinition; import org.springframework.beans.factory.annotation.Autowired; @@ -34,7 +34,7 @@ public class FhirResourceDaoStructureDefinitionR4 extends BaseHapiFhirResourceDa @Override public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theName) { - StructureDefinition output = myValidationSupport.generateSnapshot(theInput, theUrl, theWebUrl, theName); + StructureDefinition output = (StructureDefinition) myValidationSupport.generateSnapshot(myValidationSupport, theInput, theUrl, theWebUrl, theName); Validate.notNull(output); return output; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java index 277685b1a03..c2f2ae46581 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index 439e384495b..7e7be0b28be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -20,33 +20,35 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; import org.apache.commons.codec.binary.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r4.model.ValueSet.FilterOperator; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import java.util.Collections; import java.util.Date; @@ -64,16 +66,15 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao private DefaultProfileValidationSupport myDefaultProfileValidationSupport; private IValidationSupport myValidationSupport; + @Autowired + private IFhirResourceDaoCodeSystem myCodeSystemDao; @Override public void start() { super.start(); - myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChainR4" ); + myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain"); } - @Autowired - private IFhirResourceDaoCodeSystem myCodeSystemDao; - @Override public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) { ValueSet source = read(theId, theRequestDetails); @@ -87,62 +88,19 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao } private ValueSet doExpand(ValueSet theSource) { - - /* - * If all of the code systems are supported by the HAPI FHIR terminology service, let's - * use that as it's more efficient. - */ - - boolean allSystemsAreSuppportedByTerminologyService = true; - for (ConceptSetComponent next : theSource.getCompose().getInclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - for (ConceptSetComponent next : theSource.getCompose().getExclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - if (allSystemsAreSuppportedByTerminologyService) { - return myTerminologySvc.expandValueSetInMemory(theSource, null); - } - - HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport); - - ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null); - - ValueSet retVal = outcome.getValueset(); - retVal.setStatus(PublicationStatus.ACTIVE); - - return retVal; + IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(myValidationSupport, null, theSource); + validateHaveExpansionOrThrowInternalErrorException(retVal); + return (ValueSet) retVal.getValueSet(); } private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) { - boolean allSystemsAreSuppportedByTerminologyService = true; - for (ConceptSetComponent next : theSource.getCompose().getInclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - for (ConceptSetComponent next : theSource.getCompose().getExclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - if (allSystemsAreSuppportedByTerminologyService) { - return myTerminologySvc.expandValueSet(theSource, theOffset, theCount); - } - - HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport); - - ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null); - - ValueSet retVal = outcome.getValueset(); - retVal.setStatus(PublicationStatus.ACTIVE); - - return retVal; + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(theOffset) + .setCount(theCount); + IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(myValidationSupport, options, theSource); + validateHaveExpansionOrThrowInternalErrorException(retVal); + return (ValueSet) retVal.getValueSet(); } @Override @@ -284,8 +242,8 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao @Override public ValidateCodeResult validateCode(IPrimitiveType theValueSetIdentifier, IIdType theId, IPrimitiveType theCode, - IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, - CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { + IPrimitiveType theSystem, IPrimitiveType theDisplay, Coding theCoding, + CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { List valueSetIds = Collections.emptyList(); @@ -306,9 +264,9 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao if (theId != null) { vs = read(theId, theRequestDetails); } else if (haveIdentifierParam) { - vs = myDefaultProfileValidationSupport.fetchValueSet(getContext(), theValueSetIdentifier.getValue()); + vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue()); if (vs == null) { - vs = myValidationSupport.fetchValueSet(getContext(), theValueSetIdentifier.getValue()); + vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue()); if (vs == null) { throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue()); } @@ -321,7 +279,7 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao } // String code = theCode.getValue(); // String system = toStringOrNull(theSystem); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null); + IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null); if (result.isFound()) { ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay()); return retVal; @@ -331,7 +289,7 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao if (vs != null) { ValidateCodeResult result; if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) { - result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept); + result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept); } else { ValueSet expansion = doExpand(vs); List contains = expansion.getExpansion().getContains(); @@ -356,7 +314,7 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao } private ValidateCodeResult validateCodeIsInContains(List contains, String theSystem, String theCode, - Coding theCoding, CodeableConcept theCodeableConcept) { + Coding theCoding, CodeableConcept theCodeableConcept) { for (ValueSetExpansionContainsComponent nextCode : contains) { ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept); if (result != null) { @@ -409,5 +367,15 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao return retVal; } + public static void validateHaveExpansionOrThrowInternalErrorException(IValidationSupport.ValueSetExpansionOutcome theRetVal) { + if (theRetVal != null && theRetVal.getValueSet() == null) { + throw new InternalErrorException("Unable to expand ValueSet: " + theRetVal.getError()); + } + + if (theRetVal == null) { + throw new InternalErrorException("Unable to expand ValueSet"); + } + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java deleted file mode 100644 index feb0fe02960..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java +++ /dev/null @@ -1,102 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; -import java.util.Collections; -import java.util.List; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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% - */ - -@Transactional(value = TxType.REQUIRED) -public class JpaValidationSupportR4 extends BaseJpaValidationSupport implements IJpaValidationSupportR4 { - - /** - * Constructor - */ - public JpaValidationSupportR4() { - super(); - } - - - @Override - @Transactional(value = TxType.SUPPORTS) - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - return fetchResource(theCtx, CodeSystem.class, theSystem); - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) { - return fetchResource(theCtx, ValueSet.class, theSystem); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - return false; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public IValidationSupport.CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theSystemUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 94c9292757a..9cd1752555f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -21,14 +21,14 @@ package ca.uhn.fhir.jpa.dao.r5; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; @@ -39,7 +39,6 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; @@ -56,16 +55,15 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao implements IFhirResourceDaoCodeSystem { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCodeSystemR5.class); - - @Autowired - private ITermCodeSystemDao myCsDao; - @Autowired - private ValidationSupportChain myValidationSupport; @Autowired protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; @Autowired protected IdHelperService myIdHelperService; @Autowired + private ITermCodeSystemDao myCsDao; + @Autowired + private IValidationSupport myValidationSupport; + @Autowired private FhirContext myFhirContext; @Override @@ -82,7 +80,7 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -106,10 +104,10 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao implements IFhirResourceDaoMessageHeader { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java index adf834d1bcf..90e2dc5d53a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.r5; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.rest.api.CacheControlDirective; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java index 9d99eccd2a9..2813249fd75 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java @@ -1,32 +1,17 @@ package ca.uhn.fhir.jpa.dao.r5; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.ElementUtil; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.model.*; -import org.hl7.fhir.r5.utils.FHIRLexer; -import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.SearchParameter; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; -import static org.apache.commons.lang3.StringUtils.isBlank; - /* * #%L * HAPI FHIR JPA Server @@ -49,9 +34,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - public static final DefaultProfileValidationSupport VALIDATION_SUPPORT = new DefaultProfileValidationSupport(); - @Autowired - private IFhirSystemDao mySystemDao; @Autowired private ISearchParamExtractor mySearchParamExtractor; @@ -86,11 +68,12 @@ public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao status = theResource.getStatus(); List base = theResource.getBase(); + String code = theResource.getCode(); String expression = theResource.getExpression(); FhirContext context = getContext(); Enum type = theResource.getType(); - FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamExtractor, type, status, base, expression, context, getConfig()); + FhirResourceDaoSearchParameterR4.validateSearchParam(mySearchParamRegistry, mySearchParamExtractor, code, type, status, base, expression, context, getConfig()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoStructureDefinitionR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoStructureDefinitionR5.java index 0fd2b67c849..0420a861add 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoStructureDefinitionR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoStructureDefinitionR5.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.jpa.dao.r5; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; import org.hl7.fhir.r5.model.StructureDefinition; import org.springframework.beans.factory.annotation.Autowired; @@ -34,7 +34,7 @@ public class FhirResourceDaoStructureDefinitionR5 extends BaseHapiFhirResourceDa @Override public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theName) { - StructureDefinition output = myValidationSupport.generateSnapshot(theInput, theUrl, theWebUrl, theName); + StructureDefinition output = (StructureDefinition) myValidationSupport.generateSnapshot(myValidationSupport, theInput, theUrl, theWebUrl, theName); Validate.notNull(output); return output; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSubscriptionR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSubscriptionR5.java index ef7d039337b..1f4659f815c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSubscriptionR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSubscriptionR5.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.r5; * #L% */ +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java index 981cf7b9423..125c64e6990 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java @@ -20,10 +20,11 @@ package ca.uhn.fhir.jpa.dao.r5; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; @@ -35,26 +36,24 @@ import org.apache.commons.codec.binary.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Enumerations; -import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; import org.hl7.fhir.r5.model.IntegerType; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import java.util.Collections; import java.util.Date; import java.util.List; +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -64,7 +63,8 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao private ITermReadSvc myHapiTerminologySvc; @Autowired - private DefaultProfileValidationSupport myDefaultProfileValidationSupport; + @Qualifier("myDefaultProfileValidationSupport") + private IValidationSupport myDefaultProfileValidationSupport; private IValidationSupport myValidationSupport; @@ -74,7 +74,7 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao @Override public void start() { super.start(); - myValidationSupport = getApplicationContext().getBean(org.hl7.fhir.r5.hapi.ctx.IValidationSupport.class,"myJpaValidationSupportChainR5" ); + myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChain" ); } @Override @@ -90,74 +90,19 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao } private ValueSet doExpand(ValueSet theSource) { + IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(myValidationSupport, null, theSource); + validateHaveExpansionOrThrowInternalErrorException(retVal); + return (ValueSet) retVal.getValueSet(); - /* - * If all of the code systems are supported by the HAPI FHIR terminology service, let's - * use that as it's more efficient. - */ - - boolean allSystemsAreSuppportedByTerminologyService = true; - for (ConceptSetComponent next : theSource.getCompose().getInclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - for (ConceptSetComponent next : theSource.getCompose().getExclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - if (allSystemsAreSuppportedByTerminologyService) { - return (ValueSet) myTerminologySvc.expandValueSet(theSource); - } - - HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport); - - ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null); - - ValueSet retVal = outcome.getValueset(); - retVal.setStatus(PublicationStatus.ACTIVE); - - return retVal; - - // ValueSetExpansionComponent expansion = outcome.getValueset().getExpansion(); - // - // ValueSet retVal = new ValueSet(); - // retVal.getMeta().setLastUpdated(new Date()); - // retVal.setExpansion(expansion); - // return retVal; } private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) { - - /* - * If all of the code systems are supported by the HAPI FHIR terminology service, let's - * use that as it's more efficient. - */ - - boolean allSystemsAreSuppportedByTerminologyService = true; - for (ConceptSetComponent next : theSource.getCompose().getInclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - for (ConceptSetComponent next : theSource.getCompose().getExclude()) { - if (!isBlank(next.getSystem()) && !myTerminologySvc.supportsSystem(next.getSystem())) { - allSystemsAreSuppportedByTerminologyService = false; - } - } - if (allSystemsAreSuppportedByTerminologyService) { - return (ValueSet) myTerminologySvc.expandValueSet(theSource, theOffset, theCount); - } - - HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport); - - ValueSetExpansionOutcome outcome = workerContext.expand(theSource, null); - - ValueSet retVal = outcome.getValueset(); - retVal.setStatus(PublicationStatus.ACTIVE); - - return retVal; + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(theOffset) + .setCount(theCount); + IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(myValidationSupport, options, theSource); + validateHaveExpansionOrThrowInternalErrorException(retVal); + return (ValueSet) retVal.getValueSet(); } @Override @@ -321,9 +266,9 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao if (theId != null) { vs = read(theId, theRequestDetails); } else if (haveIdentifierParam) { - vs = myDefaultProfileValidationSupport.fetchValueSet(getContext(), theValueSetIdentifier.getValue()); + vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue()); if (vs == null) { - vs = myValidationSupport.fetchValueSet(getContext(), theValueSetIdentifier.getValue()); + vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue()); if (vs == null) { throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue()); } @@ -336,7 +281,7 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao } // String code = theCode.getValue(); // String system = toStringOrNull(theSystem); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null); + IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null); if (result.isFound()) { ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay()); return retVal; @@ -346,7 +291,7 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao if (vs != null) { ValidateCodeResult result; if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) { - result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept); + result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept); } else { ValueSet expansion = doExpand(vs); List contains = expansion.getExpansion().getContains(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/JpaValidationSupportR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/JpaValidationSupportR5.java deleted file mode 100644 index 742b53f2c55..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/JpaValidationSupportR5.java +++ /dev/null @@ -1,104 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r5; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.r4.BaseJpaValidationSupport; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; -import java.util.Collections; -import java.util.List; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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% - */ - -@Transactional(value = TxType.REQUIRED) -public class JpaValidationSupportR5 extends BaseJpaValidationSupport implements IJpaValidationSupportR5 { - - /** - * Constructor - */ - public JpaValidationSupportR5() { - super(); - } - - - @Override - @Transactional(value = TxType.SUPPORTS) - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - return fetchResource(theCtx, CodeSystem.class, theSystem); - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) { - return fetchResource(theCtx, ValueSet.class, theSystem); - } - - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); - } - - @Override - @Transactional(value = TxType.SUPPORTS) - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - return false; - } - - - @Override - @Transactional(value = TxType.SUPPORTS) - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theSystemUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java index e575041cd91..efadb2bf8d2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java @@ -24,12 +24,13 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.util.DeleteConflict; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PartitionEntity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PartitionEntity.java new file mode 100644 index 00000000000..002e5d32c12 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PartitionEntity.java @@ -0,0 +1,76 @@ +package ca.uhn.fhir.jpa.entity; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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 javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Entity +@Table(name = "HFJ_PARTITION", uniqueConstraints = { + @UniqueConstraint(name = "IDX_PART_NAME", columnNames = {"PART_NAME"}) +}) +public class PartitionEntity { + + public static final int MAX_NAME_LENGTH = 200; + public static final int MAX_DESC_LENGTH = 200; + + /** + * Note that unlike most PID columns in HAPI FHIR JPA, this one is an Integer, and isn't + * auto assigned. + */ + @Id + @Column(name = "PART_ID", nullable = false) + private Integer myId; + @Column(name = "PART_NAME", length = MAX_NAME_LENGTH, nullable = false) + private String myName; + @Column(name = "PART_DESC", length = MAX_DESC_LENGTH, nullable = true) + private String myDescription; + + public Integer getId() { + return myId; + } + + public PartitionEntity setId(Integer theId) { + myId = theId; + return this; + } + + public String getName() { + return myName; + } + + public PartitionEntity setName(String theName) { + myName = theName; + return this; + } + + public String getDescription() { + return myDescription; + } + + public void setDescription(String theDescription) { + myDescription = theDescription; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java index 73abe091453..ce89811024b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java @@ -47,7 +47,7 @@ public class TermCodeSystem implements Serializable { @Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH) private String myCodeSystemUri; - @OneToOne() + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CURRENT_VERSION_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_CURVER")) private TermCodeSystemVersion myCurrentVersion; @Column(name = "CURRENT_VERSION_PID", nullable = true, insertable = false, updatable = false) @@ -57,7 +57,7 @@ public class TermCodeSystem implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODESYSTEM_PID") @Column(name = "PID") private Long myPid; - @OneToOne() + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_RES")) private ResourceTable myResource; @Column(name = "RES_ID", insertable = false, updatable = false) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java index f92e421888a..c87f76e3d3e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java @@ -50,7 +50,7 @@ public class TermCodeSystemVersion implements Serializable { @Column(name = "PID") private Long myId; - @OneToOne() + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CODESYSVER_RES_ID")) private ResourceTable myResource; @@ -64,7 +64,7 @@ public class TermCodeSystemVersion implements Serializable { * This was added in HAPI FHIR 3.3.0 and is nullable just to avoid migration * issued. It should be made non-nullable at some point. */ - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_CODESYSVER_CS_ID")) private TermCodeSystem myCodeSystem; @@ -72,7 +72,7 @@ public class TermCodeSystemVersion implements Serializable { private Long myCodeSystemPid; @SuppressWarnings("unused") - @OneToOne(mappedBy = "myCurrentVersion", optional = true) + @OneToOne(mappedBy = "myCurrentVersion", optional = true, fetch = FetchType.LAZY) private TermCodeSystem myCodeSystemHavingThisVersionAsCurrentVersionIfAny; @Column(name = "CS_DISPLAY", nullable = true, updatable = false, length = MAX_VERSION_LENGTH) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 1b6aa71b777..7373c8e9952 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -20,10 +20,10 @@ package ca.uhn.fhir.jpa.entity; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -65,7 +65,7 @@ public class TermConcept implements Serializable { @Temporal(TemporalType.TIMESTAMP) @Column(name = "CONCEPT_UPDATED", nullable = true) private Date myUpdated; - @ManyToOne() + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID")) private TermCodeSystemVersion myCodeSystem; @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false) @@ -79,11 +79,11 @@ public class TermConcept implements Serializable { @Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer")) }) private String myDisplay; - @OneToMany(mappedBy = "myConcept", orphanRemoval = false) + @OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY) @Field(name = "PROPmyProperties", analyzer = @Analyzer(definition = "termConceptPropertyAnalyzer")) @FieldBridge(impl = TermConceptPropertyFieldBridge.class) private Collection myProperties; - @OneToMany(mappedBy = "myConcept", orphanRemoval = false) + @OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY) private Collection myDesignations; @Id() @SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID") @@ -388,15 +388,15 @@ public class TermConcept implements Serializable { return b.build(); } - public List toValidationProperties() { - List retVal = new ArrayList<>(); + public List toValidationProperties() { + List retVal = new ArrayList<>(); for (TermConceptProperty next : getProperties()) { switch (next.getType()) { case STRING: - retVal.add(new IContextValidationSupport.StringConceptProperty(next.getKey(), next.getValue())); + retVal.add(new IValidationSupport.StringConceptProperty(next.getKey(), next.getValue())); break; case CODING: - retVal.add(new IContextValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay())); + retVal.add(new IValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay())); break; default: throw new IllegalStateException("Don't know how to handle " + next.getType()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java index 58069ed8932..61cfcf80ee2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java @@ -39,7 +39,7 @@ public class TermConceptDesignation implements Serializable { public static final int MAX_LENGTH = 500; public static final int MAX_VAL_LENGTH = 2000; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT")) private TermConcept myConcept; @Id() @@ -62,7 +62,7 @@ public class TermConceptDesignation implements Serializable { * * @since 3.5.0 */ - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CSV")) private TermCodeSystemVersion myCodeSystemVersion; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java index a75511c57ab..2ceceabe931 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java @@ -32,14 +32,14 @@ import java.io.Serializable; public class TermConceptParentChildLink implements Serializable { private static final long serialVersionUID = 1L; - @ManyToOne() + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CHILD_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CHILD")) private TermConcept myChild; @Column(name = "CHILD_PID", insertable = false, updatable = false) private Long myChildPid; - @ManyToOne() + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CODESYSTEM_PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CS")) private TermCodeSystemVersion myCodeSystem; @@ -47,7 +47,7 @@ public class TermConceptParentChildLink implements Serializable { @Fields({@Field(name = "myCodeSystemVersionPid")}) private long myCodeSystemVersionPid; - @ManyToOne(cascade = {}) + @ManyToOne(fetch = FetchType.LAZY, cascade = {}) @JoinColumn(name = "PARENT_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_PARENT")) private TermConcept myParent; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java index 7a59b5d3c3a..13bb6b24a07 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java @@ -42,9 +42,9 @@ public class TermConceptProperty implements Serializable { private static final long serialVersionUID = 1L; private static final int MAX_LENGTH = 500; - static final int MAX_PROPTYPE_ENUM_LENGTH = 6; + public static final int MAX_PROPTYPE_ENUM_LENGTH = 6; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) private TermConcept myConcept; /** @@ -52,7 +52,7 @@ public class TermConceptProperty implements Serializable { * * @since 3.5.0 */ - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CSV")) private TermCodeSystemVersion myCodeSystemVersion; @Id() diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java index 070cbefc0b2..b422ad16233 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java @@ -22,13 +22,19 @@ package ca.uhn.fhir.jpa.graphql; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.NumberParam; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.SpecialParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import org.hl7.fhir.exceptions.FHIRException; @@ -39,21 +45,42 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.utilities.graphql.Argument; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; import org.hl7.fhir.utilities.graphql.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Nullable; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import static ca.uhn.fhir.rest.api.Constants.PARAM_FILTER; public class JpaStorageServices extends BaseHapiFhirDao implements IGraphQLStorageServices { private static final int MAX_SEARCH_SIZE = 500; + private static final Logger ourLog = LoggerFactory.getLogger(JpaStorageServices.class); private IFhirResourceDao getDao(String theResourceType) { RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(theResourceType); return myDaoRegistry.getResourceDaoOrNull(typeDef.getImplementingClass()); } + private String graphqlArgumentToSearchParam(String name) { + if (name.startsWith("_")) { + return name; + } else { + return name.replaceAll("_", "-"); + } + } + + private String searchParamToGraphqlArgument(String name) { + return name.replaceAll("-", "_"); + } + @Transactional(propagation = Propagation.NEVER) @Override public void listResources(Object theAppInfo, String theType, List theSearchParams, List theMatches) throws FHIRException { @@ -64,9 +91,25 @@ public class JpaStorageServices extends BaseHapiFhirDao implement SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(MAX_SEARCH_SIZE); + Map searchParams = mySearchParamRegistry.getActiveSearchParams(typeDef.getName()); + for (Argument nextArgument : theSearchParams) { - RuntimeSearchParam searchParam = mySearchParamRegistry.getSearchParamByName(typeDef, nextArgument.getName()); + if (nextArgument.getName().equals(PARAM_FILTER)) { + String value = nextArgument.getValues().get(0).getValue(); + params.add(PARAM_FILTER, new StringParam(value)); + continue; + } + + String searchParamName = graphqlArgumentToSearchParam(nextArgument.getName()); + RuntimeSearchParam searchParam = searchParams.get(searchParamName); + if (searchParam == null) { + Set graphqlArguments = searchParams.keySet().stream() + .map(this::searchParamToGraphqlArgument) + .collect(Collectors.toSet()); + String msg = getContext().getLocalizer().getMessageSanitized(JpaStorageServices.class, "invalidGraphqlArgument", nextArgument.getName(), new TreeSet<>(graphqlArguments)); + throw new InvalidRequestException(msg); + } for (Value nextValue : nextArgument.getValues()) { String value = nextValue.getValue(); @@ -102,7 +145,7 @@ public class JpaStorageServices extends BaseHapiFhirDao implement break; } - params.add(nextArgument.getName(), param); + params.add(searchParamName, param); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.java index 4143451d345..5a2c55bc0d4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.java @@ -21,17 +21,22 @@ package ca.uhn.fhir.jpa.interceptor; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.interceptor.api.*; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.delete.DeleteConflictOutcome; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.ResponseDetails; +import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.OperationOutcomeUtil; import org.apache.commons.lang3.Validate; @@ -41,9 +46,9 @@ import org.hl7.fhir.r4.model.OperationOutcome; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; import java.util.List; import static ca.uhn.fhir.jpa.delete.DeleteConflictService.MAX_RETRY_ATTEMPTS; @@ -71,30 +76,42 @@ public class CascadingDeleteInterceptor { private final DaoRegistry myDaoRegistry; private final IInterceptorBroadcaster myInterceptorBroadcaster; + private final FhirContext myFhirContext; /** * Constructor * * @param theDaoRegistry The DAO registry (must not be null) */ - public CascadingDeleteInterceptor(DaoRegistry theDaoRegistry, IInterceptorBroadcaster theInterceptorBroadcaster) { + public CascadingDeleteInterceptor(@Nonnull FhirContext theFhirContext, @Nonnull DaoRegistry theDaoRegistry, @Nonnull IInterceptorBroadcaster theInterceptorBroadcaster) { Validate.notNull(theDaoRegistry, "theDaoRegistry must not be null"); Validate.notNull(theInterceptorBroadcaster, "theInterceptorBroadcaster must not be null"); + Validate.notNull(theFhirContext, "theFhirContext must not be null"); + myDaoRegistry = theDaoRegistry; myInterceptorBroadcaster = theInterceptorBroadcaster; + myFhirContext = theFhirContext; } @Hook(Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS) public DeleteConflictOutcome handleDeleteConflicts(DeleteConflictList theConflictList, RequestDetails theRequest) { ourLog.debug("Have delete conflicts: {}", theConflictList); - if (!shouldCascade(theRequest)) { + if (shouldCascade(theRequest) == DeleteCascadeModeEnum.NONE) { + + // Add a message to the response + String message = myFhirContext.getLocalizer().getMessage(CascadingDeleteInterceptor.class, "noParam"); + ourLog.trace(message); + + if (theRequest != null) { + theRequest.getUserData().put(CASCADED_DELETES_FAILED_KEY, message); + } + return null; } List cascadedDeletes = getCascadedDeletesMap(theRequest, true); - for (Iterator iter = theConflictList.iterator(); iter.hasNext(); ) { - DeleteConflict next = iter.next(); + for (DeleteConflict next : theConflictList) { IdDt nextSource = next.getSourceId(); String nextSourceId = nextSource.toUnqualifiedVersionless().getValue(); @@ -176,28 +193,12 @@ public class CascadingDeleteInterceptor { /** * Subclasses may override * - * @param theRequest The REST request + * @param theRequest The REST request (may be null) * @return Returns true if cascading delete should be allowed */ - @SuppressWarnings("WeakerAccess") - protected boolean shouldCascade(RequestDetails theRequest) { - if (theRequest != null) { - String[] cascadeParameters = theRequest.getParameters().get(Constants.PARAMETER_CASCADE_DELETE); - if (cascadeParameters != null && Arrays.asList(cascadeParameters).contains(Constants.CASCADE_DELETE)) { - return true; - } - - String cascadeHeader = theRequest.getHeader(Constants.HEADER_CASCADE); - if (Constants.CASCADE_DELETE.equals(cascadeHeader)) { - return true; - } - - // Add a message to the response - String message = theRequest.getFhirContext().getLocalizer().getMessage(CascadingDeleteInterceptor.class, "noParam"); - theRequest.getUserData().put(CASCADED_DELETES_FAILED_KEY, message); - } - - return false; + @Nonnull + protected DeleteCascadeModeEnum shouldCascade(@Nullable RequestDetails theRequest) { + return RestfulServerUtils.extractDeleteCascadeParameter(theRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/IJpaValidationSupportR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IPartitionLookupSvc.java similarity index 50% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/IJpaValidationSupportR4.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IPartitionLookupSvc.java index 3e21d2eff1b..272b51834ac 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/IJpaValidationSupportR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IPartitionLookupSvc.java @@ -1,9 +1,6 @@ -package ca.uhn.fhir.jpa.dao.r4; +package ca.uhn.fhir.jpa.partition; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.StructureDefinition; - -/* +/*- * #%L * HAPI FHIR JPA Server * %% @@ -23,6 +20,28 @@ import org.hl7.fhir.r4.model.StructureDefinition; * #L% */ -public interface IJpaValidationSupportR4 extends IValidationSupport { - // nothing yet +import ca.uhn.fhir.jpa.entity.PartitionEntity; + +public interface IPartitionLookupSvc { + + /** + * This is mostly here for unit test purposes. Regular code is not expected to call this method directly. + */ + void start(); + + /** + * @throws IllegalArgumentException If the name is not known + */ + PartitionEntity getPartitionByName(String theName) throws IllegalArgumentException; + + PartitionEntity getPartitionById(Integer theId); + + void clearCaches(); + + PartitionEntity createPartition(PartitionEntity thePartition); + + PartitionEntity updatePartition(PartitionEntity thePartition); + + void deletePartition(Integer thePartitionId); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperService.java new file mode 100644 index 00000000000..083cb28d9c6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperService.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jpa.partition; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IRequestPartitionHelperService { + @Nullable + RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType); + + @Nullable + RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionLookupSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionLookupSvcImpl.java new file mode 100644 index 00000000000..696e38c5529 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionLookupSvcImpl.java @@ -0,0 +1,224 @@ +package ca.uhn.fhir.jpa.partition; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.data.IPartitionDao; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import org.apache.commons.lang3.Validate; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.PostConstruct; +import javax.transaction.Transactional; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class PartitionLookupSvcImpl implements IPartitionLookupSvc { + + public static final int DEFAULT_PERSISTED_PARTITION_ID = 0; + public static final String DEFAULT_PERSISTED_PARTITION_NAME = "DEFAULT"; + private static final String DEFAULT_PERSISTED_PARTITION_DESC = "Default partition"; + private static final Pattern PARTITION_NAME_VALID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+"); + private static final Logger ourLog = LoggerFactory.getLogger(PartitionLookupSvcImpl.class); + + @Autowired + private PlatformTransactionManager myTxManager; + @Autowired + private IPartitionDao myPartitionDao; + + private LoadingCache myNameToPartitionCache; + private LoadingCache myIdToPartitionCache; + private TransactionTemplate myTxTemplate; + @Autowired + private FhirContext myFhirCtx; + + @Override + @PostConstruct + public void start() { + myNameToPartitionCache = Caffeine + .newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(new NameToPartitionCacheLoader()); + myIdToPartitionCache = Caffeine + .newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(new IdToPartitionCacheLoader()); + myTxTemplate = new TransactionTemplate(myTxManager); + + // Create default partition definition if it doesn't already exist + myTxTemplate.executeWithoutResult(t -> { + if (myPartitionDao.findById(DEFAULT_PERSISTED_PARTITION_ID).isPresent() == false) { + ourLog.info("Creating default partition definition"); + PartitionEntity partitionEntity = new PartitionEntity(); + partitionEntity.setId(DEFAULT_PERSISTED_PARTITION_ID); + partitionEntity.setName(DEFAULT_PERSISTED_PARTITION_NAME); + partitionEntity.setDescription(DEFAULT_PERSISTED_PARTITION_DESC); + myPartitionDao.save(partitionEntity); + } + }); + + } + + @Override + public PartitionEntity getPartitionByName(String theName) { + Validate.notBlank(theName, "The name must not be null or blank"); + return myNameToPartitionCache.get(theName); + } + + @Override + public PartitionEntity getPartitionById(Integer theId) { + Validate.notNull(theId, "The ID must not be null"); + return myIdToPartitionCache.get(theId); + } + + @Override + public void clearCaches() { + myNameToPartitionCache.invalidateAll(); + myIdToPartitionCache.invalidateAll(); + } + + @Override + @Transactional + public PartitionEntity createPartition(PartitionEntity thePartition) { + validateHaveValidPartitionIdAndName(thePartition); + validatePartitionNameDoesntAlreadyExist(thePartition.getName()); + + if (thePartition.getId() == DEFAULT_PERSISTED_PARTITION_ID) { + String msg = myFhirCtx.getLocalizer().getMessage(PartitionLookupSvcImpl.class, "cantCreatePartition0"); + throw new InvalidRequestException(msg); + } + + ourLog.info("Creating new partition with ID {} and Name {}", thePartition.getId(), thePartition.getName()); + + myPartitionDao.save(thePartition); + return thePartition; + } + + @Override + @Transactional + public PartitionEntity updatePartition(PartitionEntity thePartition) { + validateHaveValidPartitionIdAndName(thePartition); + + Optional existingPartitionOpt = myPartitionDao.findById(thePartition.getId()); + if (existingPartitionOpt.isPresent() == false) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", thePartition.getId()); + throw new InvalidRequestException(msg); + } + + PartitionEntity existingPartition = existingPartitionOpt.get(); + if (!thePartition.getName().equalsIgnoreCase(existingPartition.getName())) { + validatePartitionNameDoesntAlreadyExist(thePartition.getName()); + } + + if (DEFAULT_PERSISTED_PARTITION_ID == thePartition.getId()) { + if (!DEFAULT_PERSISTED_PARTITION_NAME.equals(thePartition.getName())) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantRenameDefaultPartition"); + throw new InvalidRequestException(msg); + } + } + + existingPartition.setName(thePartition.getName()); + existingPartition.setDescription(thePartition.getDescription()); + myPartitionDao.save(existingPartition); + clearCaches(); + return existingPartition; + } + + @Override + @Transactional + public void deletePartition(Integer thePartitionId) { + Validate.notNull(thePartitionId); + + if (DEFAULT_PERSISTED_PARTITION_ID == thePartitionId) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantDeleteDefaultPartition"); + throw new InvalidRequestException(msg); + } + + Optional partition = myPartitionDao.findById(thePartitionId); + if (!partition.isPresent()) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", thePartitionId); + throw new IllegalArgumentException(msg); + } + + myPartitionDao.delete(partition.get()); + + clearCaches(); + } + + private void validatePartitionNameDoesntAlreadyExist(String theName) { + if (myPartitionDao.findForName(theName).isPresent()) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "cantCreateDuplicatePartitionName", theName); + throw new InvalidRequestException(msg); + } + } + + private void validateHaveValidPartitionIdAndName(PartitionEntity thePartition) { + if (thePartition.getId() == null || isBlank(thePartition.getName())) { + String msg = myFhirCtx.getLocalizer().getMessage(PartitionLookupSvcImpl.class, "missingPartitionIdOrName"); + throw new InvalidRequestException(msg); + } + + if (!PARTITION_NAME_VALID_PATTERN.matcher(thePartition.getName()).matches()) { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "invalidName", thePartition.getName()); + throw new InvalidRequestException(msg); + } + + } + + private class NameToPartitionCacheLoader implements @NonNull CacheLoader { + @Nullable + @Override + public PartitionEntity load(@NonNull String theName) { + return myTxTemplate.execute(t -> myPartitionDao + .findForName(theName) + .orElseThrow(() -> { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "invalidName", theName); + return new IllegalArgumentException(msg); + })); + } + } + + private class IdToPartitionCacheLoader implements @NonNull CacheLoader { + @Nullable + @Override + public PartitionEntity load(@NonNull Integer theId) { + return myTxTemplate.execute(t -> myPartitionDao + .findById(theId) + .orElseThrow(() -> { + String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionLookupSvcImpl.class, "unknownPartitionId", theId); + return new IllegalArgumentException(msg); + })); + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java new file mode 100644 index 00000000000..62512764be1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java @@ -0,0 +1,139 @@ +package ca.uhn.fhir.jpa.partition; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.model.util.ProviderConstants; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.util.ParametersUtil; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * This HAPI FHIR Server Plain Provider class provides the following operations: + *

          + *
        • partition-management-add-partition
        • + *
        • partition-management-update-partition
        • + *
        • partition-management-delete-partition
        • + *
        + */ +public class PartitionManagementProvider { + + @Autowired + private FhirContext myCtx; + @Autowired + private IPartitionLookupSvc myPartitionConfigSvc; + + /** + * Add Partition: + * + * $partition-management-add-partition + * + */ + @Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION) + public IBaseParameters addPartition( + @ResourceParam IBaseParameters theRequest, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType thePartitionId, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType thePartitionName, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType thePartitionDescription + ) { + + PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription); + PartitionEntity output = myPartitionConfigSvc.createPartition(input); + IBaseParameters retVal = prepareOutput(output); + + return retVal; + } + + /** + * Add Partition: + * + * $partition-management-update-partition + * + */ + @Operation(name = ProviderConstants.PARTITION_MANAGEMENT_UPDATE_PARTITION) + public IBaseParameters updatePartition( + @ResourceParam IBaseParameters theRequest, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType thePartitionId, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType thePartitionName, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType thePartitionDescription + ) { + + PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription); + PartitionEntity output = myPartitionConfigSvc.updatePartition(input); + IBaseParameters retVal = prepareOutput(output); + + return retVal; + } + + /** + * Add Partition: + * + * $partition-management-delete-partition + * + */ + @Operation(name = ProviderConstants.PARTITION_MANAGEMENT_DELETE_PARTITION) + public IBaseParameters updatePartition( + @ResourceParam IBaseParameters theRequest, + @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType thePartitionId + ) { + + myPartitionConfigSvc.deletePartition(thePartitionId.getValue()); + + IBaseParameters retVal = ParametersUtil.newInstance(myCtx); + ParametersUtil.addParameterToParametersString(myCtx, retVal, "message", "Success"); + + return retVal; + } + + private IBaseParameters prepareOutput(PartitionEntity theOutput) { + IBaseParameters retVal = ParametersUtil.newInstance(myCtx); + ParametersUtil.addParameterToParametersInteger(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, theOutput.getId()); + ParametersUtil.addParameterToParametersCode(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, theOutput.getName()); + if (isNotBlank(theOutput.getDescription())) { + ParametersUtil.addParameterToParametersString(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, theOutput.getDescription()); + } + return retVal; + } + + @NotNull + private PartitionEntity parseInput(@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType thePartitionId, @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType thePartitionName, @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType thePartitionDescription) { + PartitionEntity input = new PartitionEntity(); + if (thePartitionId != null) { + input.setId(thePartitionId.getValue()); + } + if (thePartitionName != null) { + input.setName(thePartitionName.getValue()); + } + if (thePartitionDescription != null) { + input.setDescription(thePartitionDescription.getValue()); + } + return input; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperService.java new file mode 100644 index 00000000000..861de5f6bd9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperService.java @@ -0,0 +1,189 @@ +package ca.uhn.fhir.jpa.partition; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashSet; + +import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooksAndReturnObject; + +public class RequestPartitionHelperService implements IRequestPartitionHelperService { + + private final HashSet myPartitioningBlacklist; + + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private IPartitionLookupSvc myPartitionConfigSvc; + @Autowired + private FhirContext myFhirContext; + @Autowired + private PartitionSettings myPartitionSettings; + + public RequestPartitionHelperService() { + myPartitioningBlacklist = new HashSet<>(); + + // Infrastructure + myPartitioningBlacklist.add("Subscription"); + myPartitioningBlacklist.add("SearchParameter"); + + // Validation + myPartitioningBlacklist.add("StructureDefinition"); + myPartitioningBlacklist.add("Questionnaire"); + + // Terminology + myPartitioningBlacklist.add("ConceptMap"); + myPartitioningBlacklist.add("CodeSystem"); + myPartitioningBlacklist.add("ValueSet"); + } + + /** + * Invoke the STORAGE_PARTITION_IDENTIFY_READ interceptor pointcut to determine the tenant for a read request + */ + @Nullable + @Override + public RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType) { + if (myPartitioningBlacklist.contains(theResourceType)) { + return null; + } + + RequestPartitionId requestPartitionId = null; + + if (myPartitionSettings.isPartitioningEnabled()) { + // Interceptor call: STORAGE_PARTITION_IDENTIFY_READ + HookParams params = new HookParams() + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params); + + validatePartition(requestPartitionId, theResourceType); + } + + return normalize(requestPartitionId); + } + + /** + * Invoke the STORAGE_PARTITION_IDENTIFY_CREATE interceptor pointcut to determine the tenant for a read request + */ + @Nullable + @Override + public RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource) { + + RequestPartitionId requestPartitionId = null; + if (myPartitionSettings.isPartitioningEnabled()) { + // Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE + HookParams params = new HookParams() + .add(IBaseResource.class, theResource) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params); + + String resourceName = myFhirContext.getResourceDefinition(theResource).getName(); + validatePartition(requestPartitionId, resourceName); + } + + return normalize(requestPartitionId); + } + + /** + * If the partition only has a name but not an ID, this method resolves the ID + * @param theRequestPartitionId + * @return + */ + private RequestPartitionId normalize(RequestPartitionId theRequestPartitionId) { + if (theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionName() != null) { + + PartitionEntity partition; + try { + partition = myPartitionConfigSvc.getPartitionByName(theRequestPartitionId.getPartitionName()); + } catch (IllegalArgumentException e) { + String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionName", theRequestPartitionId.getPartitionName()); + throw new ResourceNotFoundException(msg); + } + + if (theRequestPartitionId.getPartitionId() != null) { + Validate.isTrue(theRequestPartitionId.getPartitionId().equals(partition.getId()), "Partition name %s does not match ID %n", theRequestPartitionId.getPartitionName(), theRequestPartitionId.getPartitionId()); + return theRequestPartitionId; + } else { + return RequestPartitionId.forPartitionNameAndId(theRequestPartitionId.getPartitionName(), partition.getId(), theRequestPartitionId.getPartitionDate()); + } + } + + if (theRequestPartitionId.getPartitionId() != null) { + PartitionEntity partition; + try { + partition = myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId()); + } catch (IllegalArgumentException e) { + String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionId", theRequestPartitionId.getPartitionId()); + throw new ResourceNotFoundException(msg); + } + return RequestPartitionId.forPartitionNameAndId(partition.getName(), partition.getId(), theRequestPartitionId.getPartitionDate()); + } + + } + + // It's still possible that the partition only has a date but no name/id, + // or it could just be null + return theRequestPartitionId; + + } + + private void validatePartition(@Nullable RequestPartitionId theRequestPartitionId, @Nonnull String theResourceName) { + if (theRequestPartitionId != null && theRequestPartitionId.getPartitionId() != null) { + + // Make sure we're not using one of the conformance resources in a non-default partition + if (myPartitioningBlacklist.contains(theResourceName)) { + String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName); + throw new UnprocessableEntityException(msg); + } + + // Make sure the partition exists + try { + myPartitionConfigSvc.getPartitionById(theRequestPartitionId.getPartitionId()); + } catch (IllegalArgumentException e) { + String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "unknownPartitionId", theRequestPartitionId.getPartitionId()); + throw new InvalidRequestException(msg); + } + + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java index eda0b7aefb3..97fe1b54780 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java index 03ec15d2cbc..a0998f2821d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java @@ -20,11 +20,18 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; +import ca.uhn.fhir.rest.annotation.At; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.History; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Patch; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Since; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCompositionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCompositionDstu2.java index 92255d52592..b240ddf44bf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCompositionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCompositionDstu2.java @@ -1,8 +1,7 @@ package ca.uhn.fhir.jpa.provider; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; import ca.uhn.fhir.jpa.model.util.JpaConstants; - import ca.uhn.fhir.model.dstu2.resource.Composition; import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.annotation.Operation; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounterDstu2.java index d7d45cdfa8f..9b68616caf6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounterDstu2.java @@ -20,12 +20,15 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoEncounter; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.dstu2.resource.Encounter; import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Sort; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -41,17 +44,17 @@ public class BaseJpaResourceProviderEncounterDstu2 extends JpaResourceProviderDs javax.servlet.http.HttpServletRequest theServletRequest, - @IdParam + @IdParam ca.uhn.fhir.model.primitive.IdDt theId, - - @Description(formalDefinition="Results from this method are returned across multiple pages. This parameter controls the size of those pages.") - @OperationParam(name = Constants.PARAM_COUNT) + + @Description(formalDefinition="Results from this method are returned across multiple pages. This parameter controls the size of those pages.") + @OperationParam(name = Constants.PARAM_COUNT) ca.uhn.fhir.model.primitive.UnsignedIntDt theCount, - + @Description(shortDefinition="Only return resources which were last updated as specified by the given range") - @OperationParam(name = Constants.PARAM_LASTUPDATED, min=0, max=1) + @OperationParam(name = Constants.PARAM_LASTUPDATED, min=0, max=1) DateRangeParam theLastUpdated, - + @Sort SortSpec theSortSpec ) { @@ -71,14 +74,14 @@ public class BaseJpaResourceProviderEncounterDstu2 extends JpaResourceProviderDs javax.servlet.http.HttpServletRequest theServletRequest, - @Description(formalDefinition="Results from this method are returned across multiple pages. This parameter controls the size of those pages.") - @OperationParam(name = Constants.PARAM_COUNT) + @Description(formalDefinition="Results from this method are returned across multiple pages. This parameter controls the size of those pages.") + @OperationParam(name = Constants.PARAM_COUNT) ca.uhn.fhir.model.primitive.UnsignedIntDt theCount, - + @Description(shortDefinition="Only return resources which were last updated as specified by the given range") - @OperationParam(name = Constants.PARAM_LASTUPDATED, min=0, max=1) + @OperationParam(name = Constants.PARAM_LASTUPDATED, min=0, max=1) DateRangeParam theLastUpdated, - + @Sort SortSpec theSortSpec ) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java index a0b9ffed6ea..4dd3307311e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderPatientDstu2.java @@ -1,9 +1,28 @@ package ca.uhn.fhir.jpa.provider; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Sort; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR JPA Server @@ -24,19 +43,6 @@ import java.util.List; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; -import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; - public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu2 { /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java index 0180c6ae5d7..3daed2b4d35 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java @@ -20,16 +20,19 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.ValueSet; -import ca.uhn.fhir.model.primitive.*; +import ca.uhn.fhir.model.primitive.BooleanDt; +import ca.uhn.fhir.model.primitive.CodeDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -102,7 +105,7 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst startRequest(theServletRequest); try { IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao(); - IContextValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); if (result.isFound() == false) { throw new ResourceNotFoundException("Unable to find code[" + result.getSearchedForCode() + "] in system[" + result.getSearchedForSystem() + "]"); } @@ -139,7 +142,7 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst startRequest(theServletRequest); try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanDt(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java index 467d591e233..d139111ede9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java @@ -20,10 +20,10 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; -import ca.uhn.fhir.jpa.util.ExpungeOptions; -import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.rest.annotation.At; import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.Since; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java index a566ade6623..4c668129f13 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.annotation.GraphQL; import ca.uhn.fhir.rest.annotation.GraphQLQuery; import ca.uhn.fhir.rest.annotation.IdParam; @@ -35,8 +35,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.utilities.graphql.IGraphQLEngine; @@ -71,28 +70,28 @@ public class GraphQLProvider { * @param theValidationSupport The HAPI Validation Support object, or null * @param theStorageServices The storage services (this object will be used to retrieve various resources as required by the GraphQL engine) */ - public GraphQLProvider(@Nonnull FhirContext theFhirContext, @Nullable IContextValidationSupport theValidationSupport, @Nonnull IGraphQLStorageServices theStorageServices) { + public GraphQLProvider(@Nonnull FhirContext theFhirContext, @Nullable IValidationSupport theValidationSupport, @Nonnull IGraphQLStorageServices theStorageServices) { Validate.notNull(theFhirContext, "theFhirContext must not be null"); Validate.notNull(theStorageServices, "theStorageServices must not be null"); switch (theFhirContext.getVersion().getVersion()) { case DSTU3: { - IValidationSupport validationSupport = (IValidationSupport) theValidationSupport; - validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport()); + IValidationSupport validationSupport = theValidationSupport; + validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext)); org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport); engineFactory = () -> new org.hl7.fhir.dstu3.utils.GraphQLEngine(workerContext); break; } case R4: { - org.hl7.fhir.r4.hapi.ctx.IValidationSupport validationSupport = (org.hl7.fhir.r4.hapi.ctx.IValidationSupport) theValidationSupport; - validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport()); + IValidationSupport validationSupport = theValidationSupport; + validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext)); org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport); engineFactory = () -> new org.hl7.fhir.r4.utils.GraphQLEngine(workerContext); break; } case R5: { - org.hl7.fhir.r5.hapi.ctx.IValidationSupport validationSupport = (org.hl7.fhir.r5.hapi.ctx.IValidationSupport) theValidationSupport; - validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport()); + IValidationSupport validationSupport = theValidationSupport; + validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext)); org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport); engineFactory = () -> new org.hl7.fhir.r5.utils.GraphQLEngine(workerContext); break; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java index 1bc36a3c1d6..bd35c45392a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java @@ -19,15 +19,12 @@ package ca.uhn.fhir.jpa.provider; * limitations under the License. * #L% */ -import java.util.*; - -import javax.servlet.http.HttpServletRequest; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; @@ -50,6 +47,11 @@ import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; import org.hl7.fhir.dstu2.model.Subscription; +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java index 543d2a7dcf0..e4f7e8115bb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.composite.MetaDt; @@ -28,7 +28,15 @@ import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.primitive.BooleanDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IntegerDt; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.ValidationModeEnum; @@ -38,7 +46,12 @@ import org.hl7.fhir.instance.model.api.IIdType; import javax.servlet.http.HttpServletRequest; -import static ca.uhn.fhir.jpa.model.util.JpaConstants.*; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_ADD; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_DELETE; public class JpaResourceProviderDstu2 extends BaseJpaResourceProvider { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java index bf30d291811..47c21bd1646 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.provider; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; -import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -14,7 +14,11 @@ import ca.uhn.fhir.model.primitive.BooleanDt; import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java index 829ac7b9f9b..8aba30e107e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java @@ -22,7 +22,8 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.subscription.ISubscriptionTriggeringSvc; +import ca.uhn.fhir.jpa.model.util.ProviderConstants; +import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -37,8 +38,6 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.List; public class SubscriptionTriggeringProvider implements IResourceProvider { - public static final String RESOURCE_ID = "resourceId"; - public static final String SEARCH_URL = "searchUrl"; @Autowired private FhirContext myFhirContext; @Autowired @@ -47,8 +46,8 @@ public class SubscriptionTriggeringProvider implements IResourceProvider { @Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) public IBaseParameters triggerSubscription( - @OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "uri") List> theResourceIds, - @OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "string") List> theSearchUrls + @OperationParam(name = ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "uri") List> theResourceIds, + @OperationParam(name = ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "string") List> theSearchUrls ) { return mySubscriptionTriggeringSvc.triggerSubscription(theResourceIds, theSearchUrls, null); } @@ -56,8 +55,8 @@ public class SubscriptionTriggeringProvider implements IResourceProvider { @Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) public IBaseParameters triggerSubscription( @IdParam IIdType theSubscriptionId, - @OperationParam(name = RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "uri") List> theResourceIds, - @OperationParam(name = SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "string") List> theSearchUrls + @OperationParam(name = ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "uri") List> theResourceIds, + @OperationParam(name = ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "string") List> theSearchUrls ) { return mySubscriptionTriggeringSvc.triggerSubscription(theResourceIds, theSearchUrls, theSubscriptionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java index 9cc1ab7e8bb..2fcee77131e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java @@ -20,14 +20,21 @@ package ca.uhn.fhir.jpa.provider.dstu3; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.exceptions.FHIRException; import javax.servlet.http.HttpServletRequest; @@ -53,7 +60,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD startRequest(theServletRequest); try { IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao(); - IContextValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } catch (FHIRException e) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCompositionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCompositionDstu3.java index aef3b5345f1..e1dbbe14b26 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCompositionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCompositionDstu3.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -13,7 +13,11 @@ import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.DateRangeParam; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Composition; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.UnsignedIntType; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderConceptMapDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderConceptMapDstu3.java index 0f42082bbef..bcc7cf17e60 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderConceptMapDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderConceptMapDstu3.java @@ -20,10 +20,10 @@ package ca.uhn.fhir.jpa.provider.dstu3; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderEncounterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderEncounterDstu3.java index c291b38fc65..93866e58668 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderEncounterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderEncounterDstu3.java @@ -1,7 +1,20 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoEncounter; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import org.hl7.fhir.dstu3.model.*; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Sort; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.DateRangeParam; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.UnsignedIntType; /* * #%L @@ -23,15 +36,6 @@ import org.hl7.fhir.dstu3.model.*; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateRangeParam; - public class BaseJpaResourceProviderEncounterDstu3 extends JpaResourceProviderDstu3 { /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java index 115e1b24825..4e507bc5c63 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderPatientDstu3.java @@ -1,8 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -25,6 +23,8 @@ import org.hl7.fhir.dstu3.model.UnsignedIntType; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR JPA Server diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderStructureDefinitionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderStructureDefinitionDstu3.java index 04741a42b52..c2ff6680744 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderStructureDefinitionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderStructureDefinitionDstu3.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java index 75b3e4d646e..b1d3d521438 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderValueSetDstu3.java @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -149,7 +148,7 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst startRequest(theServletRequest); try { IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index 2652680b93b..1183ff5570a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -22,17 +22,26 @@ package ca.uhn.fhir.jpa.provider.dstu3; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.CapabilityStatement.*; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CapabilityStatement; +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.CapabilityStatement.ConditionalDeleteStatus; +import org.hl7.fhir.dstu3.model.CapabilityStatement.ResourceVersionPolicy; +import org.hl7.fhir.dstu3.model.DecimalType; import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.UriType; import javax.servlet.http.HttpServletRequest; import java.util.Collection; @@ -64,13 +73,13 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se /** * Constructor */ - public JpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { + public JpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { super(theRestfulServer); myRestfulServer = theRestfulServer; mySystemDao = theSystemDao; myDaoConfig = theDaoConfig; super.setCache(false); - setSearchParamRegistry(theSystemDao.getSearchParamRegistry()); + setSearchParamRegistry(theSearchParamRegistry); setIncludeResourceCounts(true); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java index 42aec253031..ee70c9c0fdb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java index c1f1970a0c1..6bbafe0fed4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java index 18540d5fac0..2c69bdbf54c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java @@ -20,13 +20,20 @@ package ca.uhn.fhir.jpa.provider.r4; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -55,7 +62,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4 dao = (IFhirResourceDaoCodeSystem) getDao(); - IContextValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } finally { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCompositionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCompositionR4.java index 58016d2bdb2..ab5a61be160 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCompositionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCompositionR4.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -15,7 +15,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.UnsignedIntType; import java.util.List; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderConceptMapR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderConceptMapR4.java index d5c4ffce826..eb6f7715e32 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderConceptMapR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderConceptMapR4.java @@ -20,16 +20,24 @@ package ca.uhn.fhir.jpa.provider.r4; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderEncounterR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderEncounterR4.java index 86664f14a6b..fd468b9aad8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderEncounterR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderEncounterR4.java @@ -1,7 +1,20 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoEncounter; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import org.hl7.fhir.r4.model.*; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Sort; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.DateRangeParam; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.UnsignedIntType; /* * #%L @@ -23,15 +36,6 @@ import org.hl7.fhir.r4.model.*; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateRangeParam; - public class BaseJpaResourceProviderEncounterR4 extends JpaResourceProviderR4 { /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java index b654300b501..1d3e1c0843a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java @@ -1,8 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -25,6 +23,8 @@ import org.hl7.fhir.r4.model.UnsignedIntType; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR JPA Server diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderStructureDefinitionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderStructureDefinitionR4.java index 4cf3ca560ad..796062edc47 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderStructureDefinitionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderStructureDefinitionR4.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderValueSetR4.java index 85c10708717..640de98b412 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderValueSetR4.java @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.provider.r4; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -135,7 +134,7 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4 dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java index 6454b05125f..b4a7f0bbc41 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java @@ -22,17 +22,27 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.CapabilityStatement.*; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; +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.CapabilityStatement.ConditionalDeleteStatus; +import org.hl7.fhir.r4.model.CapabilityStatement.ResourceVersionPolicy; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Enumerations.SearchParamType; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.UriType; import javax.servlet.http.HttpServletRequest; import java.util.Collection; @@ -65,14 +75,19 @@ public class JpaConformanceProviderR4 extends org.hl7.fhir.r4.hapi.rest.server.S /** * Constructor */ - public JpaConformanceProviderR4(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { + public JpaConformanceProviderR4(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { super(theRestfulServer); + + Validate.notNull(theRestfulServer); + Validate.notNull(theSystemDao); + Validate.notNull(theDaoConfig); + myRestfulServer = theRestfulServer; mySystemDao = theSystemDao; myDaoConfig = theDaoConfig; super.setCache(false); setIncludeResourceCounts(true); - setSearchParamRegistry(theSystemDao.getSearchParamRegistry()); + setSearchParamRegistry(theSearchParamRegistry); } public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java index 219d8aa5d5e..8dea988d388 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java @@ -20,10 +20,18 @@ package ca.uhn.fhir.jpa.provider.r4; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.ValidationModeEnum; @@ -31,7 +39,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Parameters; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java index c6f4d189335..685f934c397 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java @@ -1,19 +1,29 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; -import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus; import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; +import org.hl7.fhir.r4.model.StringType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java index 3f6f494ec51..38545e7d5d7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java @@ -20,13 +20,20 @@ package ca.uhn.fhir.jpa.provider.r5; * #L% */ -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -55,7 +62,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 dao = (IFhirResourceDaoCodeSystem) getDao(); - IContextValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } finally { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCompositionR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCompositionR5.java index 867e9833ad4..43efc1f74d4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCompositionR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCompositionR5.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -15,7 +15,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.Composition; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.UnsignedIntType; import java.util.List; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderConceptMapR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderConceptMapR5.java index 4e31249c355..62b3949663f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderConceptMapR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderConceptMapR5.java @@ -20,17 +20,25 @@ package ca.uhn.fhir.jpa.provider.r5; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.convertors.VersionConvertor_40_50; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.ConceptMap; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderEncounterR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderEncounterR5.java index 40e9d16e8e3..0c2812a1146 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderEncounterR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderEncounterR5.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoEncounter; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java index 4fc2ce3503c..047560078fe 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderPatientR5.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.valueset.BundleTypeEnum; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderStructureDefinitionR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderStructureDefinitionR5.java index eef88f9a17a..d94ce22f17d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderStructureDefinitionR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderStructureDefinitionR5.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderValueSetR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderValueSetR5.java index addd59aabf1..a04b8c91374 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderValueSetR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderValueSetR5.java @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.provider.r5; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -135,7 +134,7 @@ public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5 dao = (IFhirResourceDaoValueSet) getDao(); - ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); Parameters retVal = new Parameters(); retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult())); if (isNotBlank(result.getMessage())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java index ba2226b2f8c..83265e4957c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java @@ -22,17 +22,26 @@ package ca.uhn.fhir.jpa.provider.r5; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; -import org.hl7.fhir.r5.model.*; -import org.hl7.fhir.r5.model.CapabilityStatement.*; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.CapabilityStatement; +import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent; +import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.hl7.fhir.r5.model.CapabilityStatement.ConditionalDeleteStatus; +import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy; +import org.hl7.fhir.r5.model.DecimalType; import org.hl7.fhir.r5.model.Enumerations.SearchParamType; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Meta; +import org.hl7.fhir.r5.model.UriType; import javax.servlet.http.HttpServletRequest; import java.util.Collection; @@ -64,13 +73,13 @@ public class JpaConformanceProviderR5 extends org.hl7.fhir.r5.hapi.rest.server.S /** * Constructor */ - public JpaConformanceProviderR5(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { + public JpaConformanceProviderR5(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { super(); myRestfulServer = theRestfulServer; mySystemDao = theSystemDao; myDaoConfig = theDaoConfig; setIncludeResourceCounts(true); - setSearchParamRegistry(theSystemDao.getSearchParamRegistry()); + setSearchParamRegistry(theSearchParamRegistry); } public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java index ef86e4f478b..55f332a30e9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java @@ -20,10 +20,18 @@ package ca.uhn.fhir.jpa.provider.r5; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.ValidationModeEnum; @@ -31,11 +39,17 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.Meta; +import org.hl7.fhir.r5.model.Parameters; import javax.servlet.http.HttpServletRequest; -import static ca.uhn.fhir.jpa.model.util.JpaConstants.*; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_ADD; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_DELETE; public class JpaResourceProviderR5 extends BaseJpaResourceProvider { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java index 4a99a931bbe..a407d4189ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java @@ -1,19 +1,29 @@ package ca.uhn.fhir.jpa.provider.r5; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus; import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.DecimalType; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.Meta; +import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; +import org.hl7.fhir.r5.model.StringType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseHapiScheduler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseHapiScheduler.java index 24e8a2cb6e2..054f796706d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseHapiScheduler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseHapiScheduler.java @@ -27,7 +27,6 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.NotNull; import org.quartz.*; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.StdSchedulerFactory; @@ -35,6 +34,7 @@ import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -85,7 +85,7 @@ public abstract class BaseHapiScheduler implements IHapiScheduler { addProperty("org.quartz.threadPool.threadNamePrefix", getThreadPrefix()); } - @NotNull + @Nonnull private String getThreadPrefix() { return myThreadNamePrefix + "-" + myInstanceName; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java index da326e229c2..86e3298f467 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.search; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -38,6 +38,8 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider implements private DaoRegistry myDaoRegistry; @Autowired private SearchBuilderFactory mySearchBuilderFactory; + @Autowired + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; /** * Constructor @@ -57,8 +59,7 @@ public class DatabaseBackedPagingProvider extends BasePagingProvider implements @Override public synchronized IBundleProvider retrieveResultList(RequestDetails theRequestDetails, String theId) { - IFhirSystemDao systemDao = myDaoRegistry.getSystemDao(); - PersistedJpaBundleProvider provider = new PersistedJpaBundleProvider(theRequestDetails, theId, systemDao, mySearchBuilderFactory); + PersistedJpaBundleProvider provider = myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, theId); if (!provider.ensureSearchEntityLoaded()) { return null; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index b2999af3bde..f3652502ab9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -24,7 +24,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.IDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.entity.Search; @@ -36,11 +38,18 @@ import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.util.InterceptorUtil; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.api.server.*; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -49,34 +58,61 @@ import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Nonnull; import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; public class PersistedJpaBundleProvider implements IBundleProvider { private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaBundleProvider.class); - private final RequestDetails myRequest; - private FhirContext myContext; - private final IDao myDao; - private EntityManager myEntityManager; - private PlatformTransactionManager myPlatformTransactionManager; - private ISearchCoordinatorSvc mySearchCoordinatorSvc; - private ISearchCacheSvc mySearchCacheSvc; - private Search mySearchEntity; - private final String myUuid; - private boolean myCacheHit; - private IInterceptorBroadcaster myInterceptorBroadcaster; - private final SearchBuilderFactory mySearchBuilderFactory; - public PersistedJpaBundleProvider(RequestDetails theRequest, String theSearchUuid, IDao theDao, SearchBuilderFactory theSearchBuilderFactory) { + /* + * Autowired fields + */ + + @PersistenceContext + private EntityManager myEntityManager; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private SearchBuilderFactory mySearchBuilderFactory; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + protected PlatformTransactionManager myTxManager; + @Autowired + private FhirContext myContext; + @Autowired + private ISearchCoordinatorSvc mySearchCoordinatorSvc; + @Autowired + private ISearchCacheSvc mySearchCacheSvc; + + /* + * Non autowired fields (will be different for every instance + * of this class, since it's a prototype + */ + + private final RequestDetails myRequest; + private Search mySearchEntity; + private String myUuid; + private boolean myCacheHit; + + /** + * Constructor + */ + public PersistedJpaBundleProvider(RequestDetails theRequest, String theSearchUuid) { myRequest = theRequest; myUuid = theSearchUuid; - myDao = theDao; - mySearchBuilderFactory = theSearchBuilderFactory; } /** @@ -134,7 +170,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider { BaseHasResource resource; resource = next; - retVal.add(myDao.toResource(resource, true)); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(next.getResourceType()); + retVal.add(dao.toResource(resource, true)); } @@ -175,28 +212,22 @@ public class PersistedJpaBundleProvider implements IBundleProvider { } String resourceName = mySearchEntity.getResourceType(); Class resourceType = myContext.getResourceDefinition(resourceName).getImplementingClass(); - final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(myDao, resourceName, resourceType); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceName); + + final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(dao, resourceName, resourceType); final List pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest); - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + TransactionTemplate template = new TransactionTemplate(myTxManager); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); return template.execute(theStatus -> toResourceList(sb, pidsSubList)); } - private void ensureDependenciesInjected() { - if (myPlatformTransactionManager == null) { - myDao.injectDependenciesIntoBundleProvider(this); - } - } - /** * Returns false if the entity can't be found */ public boolean ensureSearchEntityLoaded() { if (mySearchEntity == null) { - ensureDependenciesInjected(); - Optional searchOpt = mySearchCacheSvc.fetchByUuid(myUuid); if (!searchOpt.isPresent()) { return false; @@ -220,13 +251,11 @@ public class PersistedJpaBundleProvider implements IBundleProvider { @Nonnull @Override public List getResources(final int theFromIndex, final int theToIndex) { - ensureDependenciesInjected(); - - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + TransactionTemplate template = new TransactionTemplate(myTxManager); template.execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { boolean entityLoaded = ensureSearchEntityLoaded(); assert entityLoaded; } @@ -264,8 +293,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider { return myCacheHit; } - void setCacheHit(boolean theCacheHit) { - myCacheHit = theCacheHit; + void setCacheHit() { + myCacheHit = true; } @Override @@ -282,12 +311,14 @@ public class PersistedJpaBundleProvider implements IBundleProvider { myEntityManager = theEntityManager; } - public void setPlatformTransactionManager(PlatformTransactionManager thePlatformTransactionManager) { - myPlatformTransactionManager = thePlatformTransactionManager; + @VisibleForTesting + public void setSearchCoordinatorSvcForUnitTest(ISearchCoordinatorSvc theSearchCoordinatorSvc) { + mySearchCoordinatorSvc = theSearchCoordinatorSvc; } - public void setSearchCoordinatorSvc(ISearchCoordinatorSvc theSearchCoordinatorSvc) { - mySearchCoordinatorSvc = theSearchCoordinatorSvc; + @VisibleForTesting + public void setTxManagerForUnitTest(PlatformTransactionManager theTxManager) { + myTxManager = theTxManager; } // Note: Leave as protected, HSPC depends on this @@ -339,7 +370,18 @@ public class PersistedJpaBundleProvider implements IBundleProvider { myInterceptorBroadcaster = theInterceptorBroadcaster; } - public void setSearchCacheSvc(ISearchCacheSvc theSearchCacheSvc) { + @VisibleForTesting + public void setSearchCacheSvcForUnitTest(ISearchCacheSvc theSearchCacheSvc) { mySearchCacheSvc = theSearchCacheSvc; } + + @VisibleForTesting + public void setDaoRegistryForUnitTest(DaoRegistry theDaoRegistry) { + myDaoRegistry = theDaoRegistry; + } + + @VisibleForTesting + public void setSearchBuilderFactoryForUnitTest(SearchBuilderFactory theSearchBuilderFactory) { + mySearchBuilderFactory = theSearchBuilderFactory; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java new file mode 100644 index 00000000000..3c695cd0b12 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.search; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.config.BaseConfig; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +public class PersistedJpaBundleProviderFactory { + + @Autowired + private ApplicationContext myApplicationContext; + + public PersistedJpaBundleProvider newInstance(RequestDetails theRequest, String theUuid) { + Object retVal = myApplicationContext.getBean(BaseConfig.PERSISTED_JPA_BUNDLE_PROVIDER, theRequest, theUuid); + return (PersistedJpaBundleProvider) retVal; + } + + public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchCoordinatorSvcImpl.SearchTask theTask, ISearchBuilder theSearchBuilder) { + return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(BaseConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index 9105001de1f..89daeb8f8ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.search; * #L% */ -import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.ISearchBuilder; -import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; @@ -31,10 +29,14 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; @@ -49,16 +51,16 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl private SearchTask mySearchTask; private ISearchBuilder mySearchBuilder; private Search mySearch; - private PlatformTransactionManager myTxManager; - // TODO KHS too many collaborators. This should be a prototype bean - public PersistedJpaSearchFirstPageBundleProvider(Search theSearch, IDao theDao, SearchBuilderFactory theSearchBuilderFactory, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, PlatformTransactionManager theTxManager, RequestDetails theRequest) { - super(theRequest, theSearch.getUuid(), theDao, theSearchBuilderFactory); + /** + * Constructor + */ + public PersistedJpaSearchFirstPageBundleProvider(Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, RequestDetails theRequest) { + super(theRequest, theSearch.getUuid()); setSearchEntity(theSearch); mySearchTask = theSearchTask; mySearchBuilder = theSearchBuilder; mySearch = theSearch; - myTxManager = theTxManager; } @Nonnull diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 49ef70e13b6..022067ac6b7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -24,7 +24,16 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.dao.IResultIterator; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchInclude; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; @@ -56,6 +65,9 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.ICachedSearchDetails; import ca.uhn.fhir.util.AsyncUtil; import ca.uhn.fhir.util.StopWatch; +import co.elastic.apm.api.ElasticApm; +import co.elastic.apm.api.Span; +import co.elastic.apm.api.Transaction; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -86,18 +98,30 @@ import javax.validation.constraints.NotNull; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; @Component("mySearchCoordinatorSvc") public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public static final int DEFAULT_SYNC_SIZE = 250; - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class); public static final String UNIT_TEST_CAPTURE_STACK = "unit_test_capture_stack"; public static final Integer INTEGER_0 = Integer.valueOf(0); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class); private final ConcurrentHashMap myIdToSearchTask = new ConcurrentHashMap<>(); @Autowired private FhirContext myContext; @@ -129,6 +153,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { * Set in {@link #start()} */ private boolean myCustomIsolationSupported; + @Autowired + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; + @Autowired + private IRequestPartitionHelperService myRequestPartitionHelperService; /** * Constructor @@ -243,7 +271,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { String resourceType = search.getResourceType(); SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search")); IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(resourceType); - SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, resourceType); + SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, requestPartitionId); myIdToSearchTask.put(search.getUuid(), task); myExecutor.submit(task); } @@ -271,18 +300,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return new ResourceGoneException(msg); } - - private void populateBundleProvider(PersistedJpaBundleProvider theRetVal) { - theRetVal.setContext(myContext); - theRetVal.setEntityManager(myEntityManager); - theRetVal.setPlatformTransactionManager(myManagedTxManager); - theRetVal.setSearchCacheSvc(mySearchCacheSvc); - theRetVal.setSearchCoordinatorSvc(this); - theRetVal.setInterceptorBroadcaster(myInterceptorBroadcaster); - } - @Override - public IBundleProvider registerSearch(final IFhirResourceDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) { + public IBundleProvider registerSearch(final IFhirResourceDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails) { final String searchUuid = UUID.randomUUID().toString(); ourLog.debug("Registering new search {}", searchUuid); @@ -295,7 +314,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) { ourLog.debug("Search {} is loading in synchronous mode", searchUuid); - return executeQuery(theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo); + return executeQuery(theResourceType, theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo); } /* @@ -366,18 +385,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { .addIfMatchesType(ServletRequestDetails.class, theRequestDetails); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params); - SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType); + + SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, theRequestDetails, requestPartitionId); myIdToSearchTask.put(search.getUuid(), task); myExecutor.submit(task); - PersistedJpaSearchFirstPageBundleProvider retVal = new PersistedJpaSearchFirstPageBundleProvider(search, theCallingDao, mySearchBuilderFactory, task, theSb, myManagedTxManager, theRequestDetails); - populateBundleProvider(retVal); + PersistedJpaSearchFirstPageBundleProvider retVal = myPersistedJpaBundleProviderFactory.newInstanceFirstPage(theRequestDetails, search, task, theSb); ourLog.debug("Search initial phase completed in {}ms", w.getMillis()); return retVal; } - @org.jetbrains.annotations.Nullable + @Nullable private IBundleProvider findCachedQuery(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString) { TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); PersistedJpaBundleProvider foundSearchProvider = txTemplate.execute(t -> { @@ -406,17 +426,14 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { .addIfMatchesType(ServletRequestDetails.class, theRequestDetails); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED, params); - PersistedJpaBundleProvider retVal = new PersistedJpaBundleProvider(theRequestDetails, searchToUse.getUuid(), theCallingDao, mySearchBuilderFactory); - retVal.setCacheHit(true); - populateBundleProvider(retVal); + PersistedJpaBundleProvider retVal = myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, searchToUse.getUuid()); + retVal.setCacheHit(); return retVal; }); - if (foundSearchProvider != null) { - return foundSearchProvider; - } - return null; + // May be null + return foundSearchProvider; } @Nullable @@ -438,7 +455,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return searchToUse; } - private IBundleProvider executeQuery(SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) { + private IBundleProvider executeQuery(String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo) { SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid); searchRuntimeDetails.setLoadSynchronous(true); @@ -450,7 +467,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { // Load the results synchronously final List pids = new ArrayList<>(); - try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails)) { + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theResourceType); + try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails, requestPartitionId)) { while (resultIter.hasNext()) { pids.add(resultIter.next()); if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) { @@ -570,6 +588,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { mySearchBuilderFactory = theSearchBuilderFactory; } + @VisibleForTesting + public void setPersistedJpaBundleProviderFactoryForUnitTest(PersistedJpaBundleProviderFactory thePersistedJpaBundleProviderFactory) { + myPersistedJpaBundleProviderFactory = thePersistedJpaBundleProviderFactory; + } + + @VisibleForTesting + public void setRequestPartitionHelperService(IRequestPartitionHelperService theRequestPartitionHelperService) { + myRequestPartitionHelperService = theRequestPartitionHelperService; + } + /** * A search task is a Callable task that runs in * a thread pool to handle an individual search. One instance @@ -592,6 +620,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private final CountDownLatch myCompletionLatch; private final ArrayList myUnsyncedPids = new ArrayList<>(); private final RequestDetails myRequest; + private final RequestPartitionId myRequestPartitionId; private Search mySearch; private boolean myAbortRequested; private int myCountSavedTotal = 0; @@ -600,12 +629,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private boolean myAdditionalPrefetchThresholdsRemaining; private List myPreviouslyAddedResourcePids; private Integer myMaxResultsToFetch; - private SearchRuntimeDetails mySearchRuntimeDetails; + private final SearchRuntimeDetails mySearchRuntimeDetails; + private final Transaction myParentTransaction; /** * Constructor */ - protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) { + protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { mySearch = theSearch; myCallingDao = theCallingDao; myParams = theParams; @@ -613,7 +643,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { myCompletionLatch = new CountDownLatch(1); mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid()); mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext())); + myRequestPartitionId = theRequestPartitionId; myRequest = theRequest; + myParentTransaction = ElasticApm.currentTransaction(); } /** @@ -841,7 +873,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { @Override public Void call() { StopWatch sw = new StopWatch(); - + Span span = myParentTransaction.startSpan("db", "query", "search"); + span.setName("FHIR Database Search"); try { // Create an initial search in the DB and give it an ID saveSearch(); @@ -897,7 +930,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { ourLog.error("Failed during search loading after {}ms", sw.getMillis(), t); } myUnsyncedPids.clear(); - Throwable rootCause = ExceptionUtils.getRootCause(t); rootCause = defaultIfNull(rootCause, t); @@ -924,23 +956,20 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FAILED, params); saveSearch(); - + span.captureException(t); } finally { myIdToSearchTask.remove(mySearch.getUuid()); myInitialCollectionLatch.countDown(); markComplete(); + span.end(); } return null; } private void doSaveSearch() { - - // This is an attempt to track down an intermittent test - // failure in testAsyncSearchLargeResultSetBigCountSameCoordinator - Object searchObj = mySearchCacheSvc.save(mySearch); - Search newSearch = (Search) searchObj; + Search newSearch = mySearchCacheSvc.save(mySearch); // mySearchDao.save is not supposed to return null, but in unit tests // it can if the mock search dao isn't set up to handle that @@ -964,7 +993,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { */ boolean wantOnlyCount = SummaryEnum.COUNT.equals(myParams.getSummaryMode()) - | INTEGER_0.equals(myParams.getCount()); + | INTEGER_0.equals(myParams.getCount()); boolean wantCount = wantOnlyCount || SearchTotalModeEnum.ACCURATE.equals(myParams.getSearchTotalMode()) || @@ -972,7 +1001,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (wantCount) { ourLog.trace("Performing count"); ISearchBuilder sb = newSearchBuilder(); - Iterator countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest); + Iterator countIterator = sb.createCountQuery(myParams, mySearch.getUuid(), myRequest, myRequestPartitionId); Long count = countIterator.next(); ourLog.trace("Got count {}", count); @@ -1048,7 +1077,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { /* * Construct the SQL query we'll be sending to the database */ - try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest)) { + try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest, myRequestPartitionId)) { assert (resultIterator != null); /* @@ -1100,8 +1129,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public class SearchContinuationTask extends SearchTask { - public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest) { - super(theSearch, theCallingDao, theParams, theResourceType, theRequest); + public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + super(theSearch, theCallingDao, theParams, theResourceType, theRequest, theRequestPartitionId); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java index 4f22d98f8f9..b1e47e47f29 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.search; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java index 8086644ab79..4566f62d435 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.search.cache; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; @@ -56,9 +56,11 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT = 500; public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS = 20000; public static final long SEARCH_CLEANUP_JOB_INTERVAL_MILLIS = 10 * DateUtils.MILLIS_PER_SECOND; + public static final int DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND = 2000; private static final Logger ourLog = LoggerFactory.getLogger(DatabaseSearchCacheSvcImpl.class); private static int ourMaximumResultsToDeleteInOneStatement = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT; private static int ourMaximumResultsToDeleteInOnePass = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS; + private static int ourMaximumSearchesToCheckForDeletionCandidacy = DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND; private static Long ourNowForUnitTests; /* * We give a bit of extra leeway just to avoid race conditions where a query result @@ -66,7 +68,6 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { * the result is to be deleted */ private long myCutoffSlack = SEARCH_CLEANUP_JOB_INTERVAL_MILLIS; - @Autowired private ISearchDao mySearchDao; @Autowired @@ -105,7 +106,6 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { return mySearchDao.findByUuidAndFetchIncludes(theUuid); } - void setSearchDaoForUnitTest(ISearchDao theSearchDao) { mySearchDao = theSearchDao; } @@ -166,18 +166,27 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { ourLog.debug("Searching for searches which are before {}", cutoff); TransactionTemplate tt = new TransactionTemplate(myTxManager); - final Slice toDelete = tt.execute(theStatus -> - mySearchDao.findWhereCreatedBefore(cutoff, new Date(), PageRequest.of(0, 2000)) - ); - assert toDelete != null; - for (final Long nextSearchToDelete : toDelete) { + // Mark searches as deleted if they should be + final Slice toMarkDeleted = tt.execute(theStatus -> + mySearchDao.findWhereCreatedBefore(cutoff, new Date(), PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy)) + ); + assert toMarkDeleted != null; + for (final Long nextSearchToDelete : toMarkDeleted) { ourLog.debug("Deleting search with PID {}", nextSearchToDelete); tt.execute(t -> { mySearchDao.updateDeleted(nextSearchToDelete, true); return null; }); + } + // Delete searches that are marked as deleted + final Slice toDelete = tt.execute(theStatus -> + mySearchDao.findDeleted(PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy)) + ); + assert toDelete != null; + for (final Long nextSearchToDelete : toDelete) { + ourLog.debug("Deleting search with PID {}", nextSearchToDelete); tt.execute(t -> { deleteSearch(nextSearchToDelete); return null; @@ -193,7 +202,6 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { } } - private void deleteSearch(final Long theSearchPid) { mySearchDao.findById(theSearchPid).ifPresent(searchToDelete -> { mySearchIncludeDao.deleteForSearch(searchToDelete.getId()); @@ -226,6 +234,11 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc { }); } + @VisibleForTesting + public static void setMaximumSearchesToCheckForDeletionCandidacyForUnitTest(int theMaximumSearchesToCheckForDeletionCandidacy) { + ourMaximumSearchesToCheckForDeletionCandidacy = theMaximumSearchesToCheckForDeletionCandidacy; + } + @VisibleForTesting public static void setMaximumResultsToDeleteInOnePassForUnitTest(int theMaximumResultsToDeleteInOnePass) { ourMaximumResultsToDeleteInOnePass = theMaximumResultsToDeleteInOnePass; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/elastic/ElasticsearchHibernatePropertiesBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/elastic/ElasticsearchHibernatePropertiesBuilder.java index 4195fafa609..3435d733f61 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/elastic/ElasticsearchHibernatePropertiesBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/elastic/ElasticsearchHibernatePropertiesBuilder.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.search.elastic; * #L% */ +import org.apache.commons.lang3.StringUtils; import org.hibernate.search.cfg.Environment; import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment; import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus; @@ -63,8 +64,12 @@ public class ElasticsearchHibernatePropertiesBuilder { theProperties.put("hibernate.search." + ElasticsearchEnvironment.ANALYSIS_DEFINITION_PROVIDER, ElasticsearchMappingProvider.class.getName()); theProperties.put("hibernate.search.default.elasticsearch.host", myRestUrl); - theProperties.put("hibernate.search.default.elasticsearch.username", myUsername); - theProperties.put("hibernate.search.default.elasticsearch.password", myPassword); + if (StringUtils.isNotBlank(myUsername)) { + theProperties.put("hibernate.search.default.elasticsearch.username", myUsername); + } + if (StringUtils.isNotBlank(myPassword)) { + theProperties.put("hibernate.search.default.elasticsearch.password", myPassword); + } theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_SCHEMA_MANAGEMENT_STRATEGY, myIndexSchemaManagementStrategy.getExternalName()); theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_MANAGEMENT_WAIT_TIMEOUT, Long.toString(myIndexManagementWaitTimeoutMillis)); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java index 1ab70ed5df4..e0031ccba0b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java @@ -22,10 +22,10 @@ package ca.uhn.fhir.jpa.search.reindex; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -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.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; @@ -67,7 +67,13 @@ import javax.transaction.Transactional; import java.util.Collection; import java.util.Date; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java index 5d8364a361f..17e945440e5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java @@ -23,9 +23,10 @@ package ca.uhn.fhir.jpa.search.warm; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; @@ -40,7 +41,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; @Component public class CacheWarmingSvcImpl implements ICacheWarmingSvc { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java index 925808c3b34..4ec2186d012 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java @@ -20,8 +20,9 @@ package ca.uhn.fhir.jpa.sp; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; import ca.uhn.fhir.jpa.util.AddRemoveCount; @@ -37,6 +38,9 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { @Autowired private ISearchParamPresentDao mySearchParamPresentDao; + @Autowired + private PartitionSettings myPartitionSettings; + @Autowired private DaoConfig myDaoConfig; @@ -63,9 +67,11 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { String paramName = next.getKey(); SearchParamPresent present = new SearchParamPresent(); + present.setPartitionSettings(myPartitionSettings); present.setResource(theResource); present.setParamName(paramName); present.setPresent(next.getValue()); + present.setPartitionId(theResource.getPartitionId()); present.calculateHashes(); newHashToPresence.put(present.getHashPresence(), present); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java deleted file mode 100644 index c4f5dcea4d4..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java +++ /dev/null @@ -1,52 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.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.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class DaoResourceRetriever implements IResourceRetriever { - private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class); - - @Autowired - FhirContext myFhirContext; - @Autowired - DaoRegistry myDaoRegistry; - - @Override - public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException { - RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType()); - IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceDef.getImplementingClass()); - return dao.read(payloadId.toVersionless()); - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java deleted file mode 100644 index 421c5b195d5..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java +++ /dev/null @@ -1,357 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.interceptor.api.Hook; -import ca.uhn.fhir.interceptor.api.Interceptor; -import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.api.IDaoRegistry; -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.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -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.SubscriptionConstants; -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.parser.DataFormatException; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.SubscriptionUtil; -import com.google.common.annotations.VisibleForTesting; -import org.hl7.fhir.dstu2.model.Subscription; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.transaction.support.TransactionTemplate; - -import javax.annotation.Nonnull; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -/** - * Responsible for transitioning subscription resources from REQUESTED to ACTIVE - * Once activated, the subscription is added to the SubscriptionRegistry. - *

        - * Also validates criteria. If invalid, rejects the subscription without persisting the subscription. - */ -@Lazy -@Interceptor -public class SubscriptionActivatingInterceptor { - private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest; - private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class); - @Autowired - private PlatformTransactionManager myTransactionManager; - @Autowired - @Qualifier(BaseConfig.TASK_EXECUTOR_NAME) - private AsyncTaskExecutor myTaskExecutor; - @Autowired - private SubscriptionRegistry mySubscriptionRegistry; - @Autowired - private DaoRegistry myDaoRegistry; - @Autowired - private FhirContext myFhirContext; - @Autowired - private SubscriptionCanonicalizer mySubscriptionCanonicalizer; - @Autowired - private DaoConfig myDaoConfig; - @Autowired - private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; - - /** - * Constructor - */ - public SubscriptionActivatingInterceptor() { - super(); - } - - public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { - // Grab the value for "Subscription.channel.type" so we can see if this - // subscriber applies.. - CanonicalSubscriptionChannelType subscriptionChannelType = mySubscriptionCanonicalizer.getChannelType(theSubscription); - - // Only activate supported subscriptions - if (subscriptionChannelType == null || !myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType.toCanonical())) { - return false; - } - - String statusString = mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription); - - if (SubscriptionConstants.REQUESTED_STATUS.equals(statusString)) { - if (TransactionSynchronizationManager.isSynchronizationActive()) { - /* - * If we're in a transaction, we don't want to try and change the status from - * requested to active within the same transaction because it's too late by - * the time we get here to make modifications to the payload. - * - * So, we register a synchronization, meaning that when the transaction is - * finished, we'll schedule a task to do this in a separate worker thread - * to avoid any possibility of conflict. - */ - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { - @Override - public void afterCommit() { - Future activationFuture = myTaskExecutor.submit(new Runnable() { - @Override - public void run() { - activateSubscription(SubscriptionConstants.ACTIVE_STATUS, theSubscription, SubscriptionConstants.REQUESTED_STATUS); - } - }); - - /* - * If we're running in a unit test, it's nice to be predictable in - * terms of order... In the real world it's a recipe for deadlocks - */ - if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) { - try { - activationFuture.get(5, TimeUnit.SECONDS); - } catch (Exception e) { - ourLog.error("Failed to activate subscription", e); - } - } - } - }); - return true; - } else { - return activateSubscription(SubscriptionConstants.ACTIVE_STATUS, theSubscription, SubscriptionConstants.REQUESTED_STATUS); - } - } else if (SubscriptionConstants.ACTIVE_STATUS.equals(statusString)) { - return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription); - } else { - // Status isn't "active" or "requested" - return mySubscriptionRegistry.unregisterSubscriptionIfRegistered(theSubscription, statusString); - } - } - - @SuppressWarnings("unchecked") - 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 { - SubscriptionUtil.setStatus(myFhirContext, subscription, theActiveStatus); - subscription = subscriptionDao.update(subscription).getResource(); - submitResourceModifiedForUpdate(subscription); - return true; - } catch (final UnprocessableEntityException e) { - ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); - SubscriptionUtil.setStatus(myFhirContext, subscription, "error"); - SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage()); - subscriptionDao.update(subscription); - return false; - } - } - - void submitResourceModifiedForUpdate(IBaseResource theNewResource) { - submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); - } - - @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED) - public void resourceUpdatedPreCommit(IBaseResource theOldResource, IBaseResource theNewResource) { - submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); - } - - @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED) - public void resourceCreatedPreCommit(IBaseResource theResource) { - submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE); - } - - @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED) - public void resourceDeletedPreCommit(IBaseResource theResource) { - submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE); - } - - @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED) - public void resourceCreatedPreStorage(IBaseResource theResource) { - if (isSubscription(theResource)) { - validateSubmittedSubscription(theResource); - } - } - - @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED) - public void resourceUpdatedPreStorage(IBaseResource theOldResource, IBaseResource theNewResource) { - if (isSubscription(theNewResource)) { - validateSubmittedSubscription(theNewResource); - } - } - - @VisibleForTesting - @SuppressWarnings("WeakerAccess") - public void setSubscriptionStrategyEvaluatorForUnitTest(SubscriptionStrategyEvaluator theSubscriptionStrategyEvaluator) { - mySubscriptionStrategyEvaluator = theSubscriptionStrategyEvaluator; - } - - @SuppressWarnings("WeakerAccess") - public void validateSubmittedSubscription(IBaseResource theSubscription) { - - CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription); - boolean finished = false; - if (subscription.getStatus() == null) { - throw new UnprocessableEntityException("Can not process submitted Subscription - Subscription.status must be populated on this server"); - } - - switch (subscription.getStatus()) { - case REQUESTED: - case ACTIVE: - break; - case ERROR: - case OFF: - case NULL: - finished = true; - break; - } - - mySubscriptionCanonicalizer.setMatchingStrategyTag(theSubscription, null); - - if (!finished) { - - String query = subscription.getCriteriaString(); - if (isBlank(query)) { - throw new UnprocessableEntityException("Subscription.criteria must be populated"); - } - - int sep = query.indexOf('?'); - if (sep <= 1) { - throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\""); - } - - String resType = query.substring(0, sep); - if (resType.contains("/")) { - throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\""); - } - - if (subscription.getChannelType() == null) { - throw new UnprocessableEntityException("Subscription.channel.type must be populated"); - } else if (subscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) { - validateChannelPayload(subscription); - validateChannelEndpoint(subscription); - } - - if (!myDaoRegistry.isResourceTypeSupported(resType)) { - throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resType); - } - - try { - SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(query); - mySubscriptionCanonicalizer.setMatchingStrategyTag(theSubscription, strategy); - } catch (InvalidRequestException | DataFormatException e) { - throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + query + " " + e.getMessage()); - } - - if (subscription.getChannelType() == null) { - throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server"); - } - - } - } - - @SuppressWarnings("WeakerAccess") - protected void validateChannelEndpoint(CanonicalSubscription theResource) { - if (isBlank(theResource.getEndpointUrl())) { - throw new UnprocessableEntityException("Rest-hook subscriptions must have Subscription.channel.endpoint defined"); - } - } - - @SuppressWarnings("WeakerAccess") - protected void validateChannelPayload(CanonicalSubscription theResource) { - if (!isBlank(theResource.getPayloadString()) && EncodingEnum.forContentType(theResource.getPayloadString()) == null) { - throw new UnprocessableEntityException("Invalid value for Subscription.channel.payload: " + theResource.getPayloadString()); - } - } - - private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum 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) { - switch (theMsg.getOperationType()) { - case DELETE: - mySubscriptionRegistry.unregisterSubscription(theMsg.getId(myFhirContext).getIdPart()); - break; - case CREATE: - case UPDATE: - activateAndRegisterSubscriptionIfRequiredInTransaction(theMsg.getNewPayload(myFhirContext)); - break; - case MANUALLY_TRIGGERED: - default: - break; - } - } - - private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) { - TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); - txTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { - activateOrRegisterSubscriptionIfRequired(theSubscription); - } - }); - } - - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - public void setSubscriptionCanonicalizerForUnitTest(SubscriptionCanonicalizer theSubscriptionCanonicalizer) { - mySubscriptionCanonicalizer = theSubscriptionCanonicalizer; - } - - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - public void setDaoRegistryForUnitTest(DaoRegistry theDaoRegistry) { - myDaoRegistry = theDaoRegistry; - } - - - @VisibleForTesting - public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) { - ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java deleted file mode 100644 index ba75e082ee9..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java +++ /dev/null @@ -1,89 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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.interceptor.api.IInterceptorService; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; -import com.google.common.annotations.VisibleForTesting; -import org.hl7.fhir.dstu2.model.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Service; - -import java.util.Set; - -@Service -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 SubscriptionChannelRegistry mySubscriptionChannelRegistry; - @Autowired - private ApplicationContext myApplicationContext; - @Autowired - private IInterceptorService myInterceptorRegistry; - - public void registerInterceptors() { - Set supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes(); - - if (supportedSubscriptionTypes.isEmpty()) { - ourLog.info("Subscriptions are disabled on this server. Subscriptions will not be activated and incoming resources will not be matched against subscriptions."); - } else { - loadSubscriptions(); - ourLog.info("Registering subscription activating interceptor"); - myInterceptorRegistry.registerInterceptor(mySubscriptionActivatingInterceptor); - if (myDaoConfig.isSubscriptionMatchingEnabled()) { - mySubscriptionMatcherInterceptor.start(); - ourLog.info("Registering subscription matcher interceptor"); - myInterceptorRegistry.registerInterceptor(mySubscriptionMatcherInterceptor); - } - } - } - - private void loadSubscriptions() { - ourLog.info("Loading subscriptions into the SubscriptionRegistry..."); - // Load active subscriptions into the SubscriptionRegistry and activate their channels - SubscriptionLoader loader = myApplicationContext.getBean(SubscriptionLoader.class); - loader.syncSubscriptions(); - ourLog.info("...{} subscriptions loaded", mySubscriptionRegistry.size()); - ourLog.info("...{} subscription channels started", mySubscriptionChannelRegistry.size()); - } - - @VisibleForTesting - void unregisterInterceptorsForUnitTest() { - myInterceptorRegistry.unregisterInterceptor(mySubscriptionActivatingInterceptor); - myInterceptorRegistry.unregisterInterceptor(mySubscriptionMatcherInterceptor); - mySubscriptionMatcherInterceptor.preDestroy(); - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java deleted file mode 100644 index 6931bfd99e9..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.dbcache; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 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.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; -import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class DaoSubscriptionProvider implements ISubscriptionProvider { - @Autowired - DaoRegistry myDaoRegistry; - @Autowired - private SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor; - - @Override - public IBundleProvider search(SearchParameterMap theMap) { - IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); - return subscriptionDao.search(theMap); - } - - @Override - public boolean loadSubscription(IBaseResource theResource) { - return mySubscriptionActivatingInterceptor.activateOrRegisterSubscriptionIfRequired(theResource); - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index 5a1ed416a51..198213e5385 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -21,14 +21,16 @@ package ca.uhn.fhir.jpa.term; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.model.TranslationQuery; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.entity.*; @@ -47,11 +49,13 @@ import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.ValidateUtil; +import ca.uhn.fhir.util.VersionIndependentConcept; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.annotations.VisibleForTesting; @@ -63,7 +67,6 @@ import org.apache.lucene.index.Term; import org.apache.lucene.queries.TermsQuery; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.MultiPhraseQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.RegexpQuery; import org.apache.lucene.search.TermQuery; @@ -80,11 +83,10 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.quartz.JobExecutionContext; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.transaction.PlatformTransactionManager; @@ -118,11 +120,10 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNoneBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationContextAware { +public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public static final int DEFAULT_FETCH_SIZE = 250; - public static final String VALUESET_LANGUAGES = "http://hl7.org/fhir/ValueSet/languages"; - public static final String VALUESET_MIMETYPES = "http://hl7.org/fhir/ValueSet/mimetypes"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class); + private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions(); private static boolean ourLastResultsFromTranslationCache; // For testing. private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. @Autowired @@ -157,11 +158,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo private ITermCodeSystemVersionDao myCodeSystemVersionDao; @Autowired private DaoConfig myDaoConfig; - private IFhirResourceDaoValueSet myValueSetResourceDao; private Cache> myTranslationCache; private Cache> myTranslationWithReverseCache; private int myFetchSize = DEFAULT_FETCH_SIZE; - private ApplicationContext myApplicationContext; private TransactionTemplate myTxTemplate; @Autowired private PlatformTransactionManager myTransactionManager; @@ -177,7 +176,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo private ITermDeferredStorageSvc myDeferredStorageSvc; @Autowired(required = false) private ITermCodeSystemStorageSvc myConceptStorageSvc; - private IContextValidationSupport myValidationSupport; + @Autowired + private ApplicationContext myApplicationContext; + private volatile IValidationSupport myJpaValidationSupport; + private volatile IValidationSupport myValidationSupport; + + @Override + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + return supportsSystem(theSystem); + } private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri(); @@ -294,9 +301,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo deleteValueSet(theResourceTable); } - @Override - @Transactional(propagation = Propagation.REQUIRED) - public ValueSet expandValueSetInMemory(ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull) { + private ValueSet expandValueSetInMemory(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull) { int maxCapacity = myDaoConfig.getMaximumExpansionSize(); ValueSetExpansionComponentWithConceptAccumulator expansionComponent = new ValueSetExpansionComponentWithConceptAccumulator(myContext, maxCapacity); @@ -305,7 +310,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo AtomicInteger codeCounter = new AtomicInteger(0); - expandValueSet(theValueSetToExpand, expansionComponent, codeCounter, theWantConceptOrNull); + expandValueSet(theExpansionOptions, theValueSetToExpand, expansionComponent, codeCounter, theWantConceptOrNull); expansionComponent.setTotal(codeCounter.get()); @@ -316,24 +321,33 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return valueSet; } + @Override + public List expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet) { + // TODO: DM 2019-09-10 - This is problematic because an incorrect URL that matches ValueSet.id will not be found in the terminology tables but will yield a ValueSet here. Depending on the ValueSet, the expansion may time-out. + + ValueSet valueSet = fetchCanonicalValueSetFromCompleteContext(theValueSet); + if (valueSet == null) { + throwInvalidValueSet(theValueSet); + } + + return expandValueSetAndReturnVersionIndependentConcepts(theExpansionOptions, valueSet, null); + } + @Override @Transactional(propagation = Propagation.REQUIRED) - public ValueSet expandValueSet(ValueSet theValueSetToExpand, int theOffset, int theCount) { + public ValueSet expandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSetToExpand, "ValueSet to expand can not be null"); Optional optionalTermValueSet; - if (theValueSetToExpand.hasId()) { - ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSetToExpand.getIdElement()); - optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong()); - } else if (theValueSetToExpand.hasUrl()) { + if (theValueSetToExpand.hasUrl()) { optionalTermValueSet = myValueSetDao.findByUrl(theValueSetToExpand.getUrl()); } else { - throw new UnprocessableEntityException("ValueSet to be expanded must provide either ValueSet.id or ValueSet.url"); + optionalTermValueSet = Optional.empty(); } if (!optionalTermValueSet.isPresent()) { - ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand)); - return expandValueSetInMemory(theValueSetToExpand, null); // In-memory expansion. + ourLog.debug("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand)); + return expandValueSetInMemory(theExpansionOptions, theValueSetToExpand, null); // In-memory expansion. } TermValueSet termValueSet = optionalTermValueSet.get(); @@ -341,14 +355,17 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo if (termValueSet.getExpansionStatus() != TermValueSetPreExpansionStatusEnum.EXPANDED) { ourLog.warn("{} is present in terminology tables but not ready for persistence-backed invocation of operation $expand. Will perform in-memory expansion without parameters. Current status: {} | {}", getValueSetInfo(theValueSetToExpand), termValueSet.getExpansionStatus().name(), termValueSet.getExpansionStatus().getDescription()); - return expandValueSetInMemory(theValueSetToExpand, null); // In-memory expansion. + return expandValueSetInMemory(theExpansionOptions, theValueSetToExpand, null); // In-memory expansion. } ValueSet.ValueSetExpansionComponent expansionComponent = new ValueSet.ValueSetExpansionComponent(); expansionComponent.setIdentifier(UUID.randomUUID().toString()); expansionComponent.setTimestamp(new Date()); - populateExpansionComponent(expansionComponent, termValueSet, theOffset, theCount); + ValueSetExpansionOptions expansionOptions = provideExpansionOptions(theExpansionOptions); + int offset = expansionOptions.getOffset(); + int count = expansionOptions.getCount(); + populateExpansionComponent(expansionComponent, termValueSet, offset, count); ValueSet valueSet = new ValueSet(); valueSet.setStatus(Enumerations.PublicationStatus.ACTIVE); @@ -436,12 +453,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo @Override @Transactional(propagation = Propagation.REQUIRED) - public void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { - expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0), null); + public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { + expandValueSet(theExpansionOptions, theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0), null); } @SuppressWarnings("ConstantConditions") - private void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter, VersionIndependentConcept theWantConceptOrNull) { + private void expandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter, VersionIndependentConcept theWantConceptOrNull) { Set addedCodes = new HashSet<>(); StopWatch sw = new StopWatch(); @@ -455,7 +472,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo int queryIndex = i; Boolean shouldContinue = myTxTemplate.execute(t -> { boolean add = true; - return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex, theWantConceptOrNull); + return expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex, theWantConceptOrNull); }); if (!shouldContinue) { break; @@ -476,7 +493,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo int queryIndex = i; Boolean shouldContinue = myTxTemplate.execute(t -> { boolean add = false; - return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex, null); + return expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex, null); }); if (!shouldContinue) { break; @@ -527,13 +544,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return sb.toString(); } - protected List expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4, VersionIndependentConcept theWantConceptOrNull) { - org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSetInMemory(theValueSetToExpandR4, theWantConceptOrNull).getExpansion(); + protected List expandValueSetAndReturnVersionIndependentConcepts(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpandR4, VersionIndependentConcept theWantConceptOrNull) { + org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSetInMemory(theExpansionOptions, theValueSetToExpandR4, theWantConceptOrNull).getExpansion(); ArrayList retVal = new ArrayList<>(); for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent nextContains : expandedR4.getContains()) { - retVal.add( - new VersionIndependentConcept(nextContains.getSystem(), nextContains.getCode())); + retVal.add(new VersionIndependentConcept(nextContains.getSystem(), nextContains.getCode(), nextContains.getDisplay())); } return retVal; } @@ -541,7 +557,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo /** * @return Returns true if there are potentially more results to process. */ - private Boolean expandValueSetHandleIncludeOrExclude(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) { + private Boolean expandValueSetHandleIncludeOrExclude(@Nullable ValueSetExpansionOptions theExpansionOptions, IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) { String system = theIncludeOrExclude.getSystem(); boolean hasSystem = isNotBlank(system); @@ -549,7 +565,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo if (hasSystem) { - if (theWantConceptOrNull != null && theWantConceptOrNull.getSystem() != null && !Constants.codeSystemNotNeeded(theWantConceptOrNull.getSystem()) && !system.equals(theWantConceptOrNull.getSystem())) { + if (theWantConceptOrNull != null && theWantConceptOrNull.getSystem() != null && !system.equals(theWantConceptOrNull.getSystem())) { return false; } @@ -678,7 +694,17 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo } else { // No CodeSystem matching the URL found in the database. - CodeSystem codeSystemFromContext = getCodeSystemFromContext(system); + CodeSystem codeSystemFromContext = fetchCanonicalCodeSystemFromCompleteContext(system); + if (codeSystemFromContext == null) { + String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", system); + if (provideExpansionOptions(theExpansionOptions).isFailOnMissingCodeSystem()) { + throw new PreconditionFailedException(msg); + } else { + ourLog.warn(msg); + theValueSetCodeAccumulator.addMessage(msg); + return false; + } + } if (!theIncludeOrExclude.getConcept().isEmpty()) { for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) { @@ -686,30 +712,16 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo if (theWantConceptOrNull == null || theWantConceptOrNull.getCode().equals(nextCode)) { if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) { - if (codeSystemFromContext != null) { - CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode); - if (code != null) { - String display = code.getDisplay(); - addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, nextCode, display); - } - } else { - - // This code just plain doesn't exist in any known codesystem, but the valueset - // explicitly includes it. We'll trust the valueset in this case. This happens for - // codesytems such a USPS. There is probably a better way to handle this... - addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, nextCode, null); + CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode); + if (code != null) { + String display = code.getDisplay(); + addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, nextCode, display); } + } } } } else { - if (codeSystemFromContext == null) { - String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", system); - ourLog.warn(msg); - theValueSetCodeAccumulator.addMessage(msg); - return false; - } - List concept = codeSystemFromContext.getConcept(); addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd, theWantConceptOrNull); } @@ -721,7 +733,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo for (CanonicalType nextValueSet : theIncludeOrExclude.getValueSet()) { ourLog.debug("Starting {} expansion around ValueSet: {}", (theAdd ? "inclusion" : "exclusion"), nextValueSet.getValueAsString()); - List expanded = expandValueSet(nextValueSet.getValueAsString()); + List expanded = expandValueSet(theExpansionOptions, nextValueSet.getValueAsString()); Map uriToCodeSystem = new HashMap<>(); for (VersionIndependentConcept nextConcept : expanded) { @@ -743,7 +755,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo // This will happen if we're expanding against a built-in (part of FHIR) ValueSet that // isn't actually in the database anywhere Collection emptyCollection = Collections.emptyList(); - addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, emptyCollection, theAdd, theCodeCounter, nextConcept.getSystem(), nextConcept.getCode(), null); + addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, emptyCollection, theAdd, theCodeCounter, nextConcept.getSystem(), nextConcept.getCode(), nextConcept.getDisplay()); } } if (isNoneBlank(nextConcept.getSystem(), nextConcept.getCode()) && !theAdd && theAddedCodes.remove(nextConcept.getSystem() + "|" + nextConcept.getCode())) { @@ -762,6 +774,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo } + private @Nonnull + ValueSetExpansionOptions provideExpansionOptions(@Nullable ValueSetExpansionOptions theExpansionOptions) { + if (theExpansionOptions != null) { + return theExpansionOptions; + } else { + return DEFAULT_EXPANSION_OPTIONS; + } + } + private void addOrRemoveCode(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, boolean theAdd, String theSystem, String theCode, String theDisplay) { if (theAdd && theAddedCodes.add(theSystem + "|" + theCode)) { theValueSetCodeAccumulator.includeConcept(theSystem, theCode, theDisplay); @@ -1082,7 +1103,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo if (theValueSetCodeAccumulator instanceof ValueSetExpansionComponentWithConceptAccumulator) { Validate.isTrue(((ValueSetExpansionComponentWithConceptAccumulator) theValueSetCodeAccumulator).getParameter().isEmpty(), "Can not expand ValueSet with parameters - Hibernate Search is not enabled on this server."); } - + Validate.isTrue(theInclude.getFilter().isEmpty(), "Can not expand ValueSet with filters - Hibernate Search is not enabled on this server."); Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server."); @@ -1094,7 +1115,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo } for (ValueSet.ConceptReferenceComponent next : theInclude.getConcept()) { - if (!theSystem.equals(theInclude.getSystem()) && !Constants.codeSystemNotNeeded(theSystem)) { + if (!theSystem.equals(theInclude.getSystem()) && isNotBlank(theSystem)) { continue; } addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, null, theAdd, theCodeCounter, theSystem, next.getCode(), next.getDisplay()); @@ -1124,7 +1145,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return true; } - protected ValidateCodeResult validateCodeIsInPreExpandedValueSet( + protected IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet( + ValidationOptions theValidationOptions, ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required"); @@ -1132,7 +1154,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo List concepts = new ArrayList<>(); if (isNotBlank(theCode)) { - if (Constants.codeSystemNotNeeded(theSystem)) { + if (theValidationOptions.isGuessSystem()) { concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode)); } else if (isNotBlank(theSystem)) { concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode)); @@ -1154,12 +1176,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo for (TermValueSetConcept concept : concepts) { if (isNotBlank(theDisplay) && theDisplay.equals(concept.getDisplay())) { - return new ValidateCodeResult(true, "Validation succeeded", concept.getDisplay()); + return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concept.getDisplay()); } } if (!concepts.isEmpty()) { - return new ValidateCodeResult(true, "Validation succeeded", concepts.get(0).getDisplay()); + return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concepts.get(0).getDisplay()); } return null; @@ -1246,6 +1268,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return retVal; } + @Transactional @Override public List findCodesAbove(String theSystem, String theCode) { TermCodeSystem cs = getCodeSystem(theSystem); @@ -1277,6 +1300,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return retVal; } + @Transactional @Override public List findCodesBelow(String theSystem, String theCode) { TermCodeSystem cs = getCodeSystem(theSystem); @@ -1293,17 +1317,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return myCodeSystemDao.findByCodeSystemUri(theSystem); } - @Override - public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { - myApplicationContext = theApplicationContext; - } - @PostConstruct public void start() { - myValueSetResourceDao = myApplicationContext.getBean(IFhirResourceDaoValueSet.class); - if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { - myValidationSupport = myApplicationContext.getBean(IContextValidationSupport.class); - } RuleBasedTransactionAttribute rules = new RuleBasedTransactionAttribute(); rules.getRollbackRules().add(new NoRollbackRuleAttribute(ExpansionTooCostlyException.class)); myTxTemplate = new TransactionTemplate(myTransactionManager, rules); @@ -1337,16 +1352,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo mySchedulerService.scheduleClusteredJob(10 * DateUtils.MILLIS_PER_MINUTE, vsJobDefinition); } - public static class Job implements HapiJob { - @Autowired - private ITermReadSvc myTerminologySvc; - - @Override - public void execute(JobExecutionContext theContext) { - myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables(); - } - } - @Override @Transactional public void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap) { @@ -1492,7 +1497,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo TermValueSet refreshedValueSetToExpand = myValueSetDao.findById(valueSetToExpand.getId()).get(); return getValueSetFromResourceTable(refreshedValueSetToExpand.getResource()); }); - expandValueSet(valueSet, new ValueSetConceptAccumulator(valueSetToExpand, myValueSetDao, myValueSetConceptDao, myValueSetConceptDesignationDao)); + expandValueSet(null, valueSet, new ValueSetConceptAccumulator(valueSetToExpand, myValueSetDao, myValueSetConceptDao, myValueSetConceptDesignationDao)); // We are done with this ValueSet. txTemplate.execute(t -> { @@ -1513,7 +1518,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo } private boolean isNotSafeToPreExpandValueSets() { - return !myDeferredStorageSvc.isStorageQueueEmpty(); + return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(); } protected abstract ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable); @@ -1601,14 +1606,14 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet); - protected IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { + protected IValidationSupport.LookupCodeResult lookupCode(String theSystem, String theCode) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); return txTemplate.execute(t -> { Optional codeOpt = findCode(theSystem, theCode); if (codeOpt.isPresent()) { TermConcept code = codeOpt.get(); - IContextValidationSupport.LookupCodeResult result = new IContextValidationSupport.LookupCodeResult(); + IValidationSupport.LookupCodeResult result = new IValidationSupport.LookupCodeResult(); result.setCodeSystemDisplayName(code.getCodeSystemVersion().getCodeSystemDisplayName()); result.setCodeSystemVersion(code.getCodeSystemVersion().getCodeSystemVersionId()); result.setSearchedForSystem(theSystem); @@ -1617,7 +1622,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo result.setCodeDisplay(code.getDisplay()); for (TermConceptDesignation next : code.getDesignations()) { - IContextValidationSupport.ConceptDesignation designation = new IContextValidationSupport.ConceptDesignation(); + IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation(); designation.setLanguage(next.getLanguage()); designation.setUseSystem(next.getUseSystem()); designation.setUseCode(next.getUseCode()); @@ -1628,10 +1633,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo for (TermConceptProperty next : code.getProperties()) { if (next.getType() == TermConceptPropertyTypeEnum.CODING) { - IContextValidationSupport.CodingConceptProperty property = new IContextValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay()); + IValidationSupport.CodingConceptProperty property = new IValidationSupport.CodingConceptProperty(next.getKey(), next.getCodeSystem(), next.getValue(), next.getDisplay()); result.getProperties().add(property); } else if (next.getType() == TermConceptPropertyTypeEnum.STRING) { - IContextValidationSupport.StringConceptProperty property = new IContextValidationSupport.StringConceptProperty(next.getKey(), next.getValue()); + IValidationSupport.StringConceptProperty property = new IValidationSupport.StringConceptProperty(next.getKey(), next.getValue()); result.getProperties().add(property); } else { throw new InternalErrorException("Unknown type: " + next.getType()); @@ -1663,13 +1668,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo @Override public boolean supportsSystem(String theSystem) { - - // Validation with only a code but no system can happen when validating against a - // valueset, which only the full term service can handle - if (theSystem == null || Constants.codeSystemNotNeeded(theSystem)) { - return true; - } - TermCodeSystem cs = getCodeSystem(theSystem); return cs != null; } @@ -1873,8 +1871,19 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSet)); } - Optional validateCodeInValueSet(String theValueSetUrl, String theCodeSystem, String theCode) { - IBaseResource valueSet = myValidationSupport.fetchValueSet(myContext, theValueSetUrl); + @Override + public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + + IPrimitiveType urlPrimitive = myContext.newTerser().getSingleValueOrNull(theValueSet, "url", IPrimitiveType.class); + String url = urlPrimitive.getValueAsString(); + if (isNotBlank(url)) { + return validateCode(theRootValidationSupport, theOptions, theCodeSystem, theCode, theDisplay, url); + } + return null; + } + + Optional validateCodeInValueSet(IValidationSupport theValidationSupport, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode) { + IBaseResource valueSet = theValidationSupport.fetchValueSet(theValueSetUrl); // If we don't have a PID, this came from some source other than the JPA // database, so we don't need to check if it's pre-expanded or not @@ -1882,7 +1891,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet); if (pid != null) { if (isValueSetPreExpandedForCodeValidation(valueSet)) { - ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(valueSet, theCodeSystem, theCode, null, null, null); + IFhirResourceDaoValueSet.ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(new ValidationOptions(), valueSet, theCodeSystem, theCode, null, null, null); if (outcome != null && outcome.isResult()) { return Optional.of(new VersionIndependentConcept(theCodeSystem, theCode)); } @@ -1892,13 +1901,148 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo ValueSet canonicalValueSet = toCanonicalValueSet(valueSet); VersionIndependentConcept wantConcept = new VersionIndependentConcept(theCodeSystem, theCode); - List expansionOutcome = expandValueSetAndReturnVersionIndependentConcepts(canonicalValueSet, wantConcept); + ValueSetExpansionOptions expansionOptions = new ValueSetExpansionOptions() + .setFailOnMissingCodeSystem(false); + + List expansionOutcome = expandValueSetAndReturnVersionIndependentConcepts(expansionOptions, canonicalValueSet, wantConcept); return expansionOutcome .stream() - .filter(t -> (Constants.codeSystemNotNeeded(theCodeSystem) || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode)) + .filter(t -> (theValidationOptions.isInferSystem() || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode)) .findFirst(); } + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + IValidationSupport jpaValidationSupport = provideJpaValidationSupport(); + return jpaValidationSupport.fetchCodeSystem(theSystem); + } + + @Override + public CodeSystem fetchCanonicalCodeSystemFromCompleteContext(String theSystem) { + IValidationSupport validationSupport = provideValidationSupport(); + IBaseResource codeSystem = validationSupport.fetchCodeSystem(theSystem); + if (codeSystem != null) { + codeSystem = toCanonicalCodeSystem(codeSystem); + } + return (CodeSystem) codeSystem; + } + + @Nonnull + private IValidationSupport provideJpaValidationSupport() { + IValidationSupport jpaValidationSupport = myJpaValidationSupport; + if (jpaValidationSupport == null) { + jpaValidationSupport = myApplicationContext.getBean("myJpaValidationSupport", IValidationSupport.class); + myJpaValidationSupport = jpaValidationSupport; + } + return jpaValidationSupport; + } + + @Nonnull + private IValidationSupport provideValidationSupport() { + IValidationSupport validationSupport = myValidationSupport; + if (validationSupport == null) { + validationSupport = myApplicationContext.getBean(IValidationSupport.class); + myValidationSupport = validationSupport; + } + return validationSupport; + } + + public ValueSet fetchCanonicalValueSetFromCompleteContext(String theSystem) { + IValidationSupport validationSupport = provideValidationSupport(); + IBaseResource valueSet = validationSupport.fetchValueSet(theSystem); + if (valueSet != null) { + valueSet = toCanonicalValueSet(valueSet); + } + return (ValueSet) valueSet; + } + + protected abstract CodeSystem toCanonicalCodeSystem(IBaseResource theCodeSystem); + + @Override + public IBaseResource fetchValueSet(String theValueSetUrl) { + return provideJpaValidationSupport().fetchValueSet(theValueSetUrl); + } + + @Override + public FhirContext getFhirContext() { + return myContext; + } + + private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getConcept(); + for (CodeSystem.ConceptDefinitionComponent next : conceptList) { + addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); + } + } + + @Override + public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + CodeSystem system = (CodeSystem) fetchCanonicalCodeSystemFromCompleteContext(theSystem); + if (system != null) { + findCodesAbove(system, theSystem, theCode, retVal); + } + return retVal; + } + + private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getConcept(); + findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); + } + + private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { + for (CodeSystem.ConceptDefinitionComponent next : conceptList) { + if (theCode.equals(next.getCode())) { + addAllChildren(theSystemString, next, theListToPopulate); + } else { + findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); + } + } + } + + @Override + public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + CodeSystem system = (CodeSystem) fetchCanonicalCodeSystemFromCompleteContext(theSystem); + if (system != null) { + findCodesBelow(system, theSystem, theCode, retVal); + } + return retVal; + } + + private void addAllChildren(String theSystemString, CodeSystem.ConceptDefinitionComponent theCode, List theListToPopulate) { + if (isNotBlank(theCode.getCode())) { + theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); + } + for (CodeSystem.ConceptDefinitionComponent nextChild : theCode.getConcept()) { + addAllChildren(theSystemString, nextChild, theListToPopulate); + } + } + + private boolean addTreeIfItContainsCode(String theSystemString, CodeSystem.ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { + boolean foundCodeInChild = false; + for (CodeSystem.ConceptDefinitionComponent nextChild : theNext.getConcept()) { + foundCodeInChild |= addTreeIfItContainsCode(theSystemString, nextChild, theCode, theListToPopulate); + } + + if (theCode.equals(theNext.getCode()) || foundCodeInChild) { + theListToPopulate.add(new VersionIndependentConcept(theSystemString, theNext.getCode())); + return true; + } + + return false; + } + + public static class Job implements HapiJob { + @Autowired + private ITermReadSvc myTerminologySvc; + + @Override + public void execute(JobExecutionContext theContext) { + myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables(); + } + } + static List toPersistedConcepts(List theConcept, TermCodeSystemVersion theCodeSystemVersion) { ArrayList retVal = new ArrayList<>(); @@ -1960,13 +2104,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return termConcept; } - private static void extractLinksFromConceptAndChildren(TermConcept theConcept, List theLinks) { - theLinks.addAll(theConcept.getParents()); - for (TermConceptParentChildLink child : theConcept.getChildren()) { - extractLinksFromConceptAndChildren(child.getChild(), theLinks); - } - } - @NotNull private static VersionIndependentConcept toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, IBaseCoding theCodingType) { String code = theCodeType != null ? theCodeType.getValueAsString() : null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index 3b36c1a97ca..9129553834c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -23,7 +23,8 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.IHapiJpaRepository; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; @@ -45,7 +46,6 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; -import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -53,11 +53,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.ValidateUtil; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; import org.apache.commons.lang3.Validate; -import org.hibernate.ScrollMode; -import org.hibernate.ScrollableResults; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ConceptMap; @@ -67,7 +63,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Propagation; @@ -78,11 +73,6 @@ import javax.annotation.Nonnull; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -127,11 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { @Override public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) { - return getValueSetResourcePid(theIdType, null); - } - - private ResourcePersistentId getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { - return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); + return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart()); } @Transactional @@ -154,7 +140,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { TermCodeSystemVersion csv = cs.getCurrentVersion(); Validate.notNull(csv); - CodeSystem codeSystem = myTerminologySvc.getCodeSystemFromContext(theSystem); + CodeSystem codeSystem = myTerminologySvc.fetchCanonicalCodeSystemFromCompleteContext(theSystem); if (codeSystem.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { throw new InvalidRequestException("CodeSystem with url[" + Constants.codeSystemWithDefaultDescription(theSystem) + "] can not apply a delta - wrong content mode: " + codeSystem.getContent()); } @@ -165,11 +151,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { IIdType codeSystemId = cs.getResource().getIdDt(); UploadStatistics retVal = new UploadStatistics(codeSystemId); + HashMap codeToConcept = new HashMap<>(); // Add root concepts for (TermConcept nextRootConcept : theAdditions.getRootConcepts()) { List parentCodes = Collections.emptyList(); - addConcept(csv, parentCodes, nextRootConcept, retVal, true, 0); + addConceptInHierarchy(csv, parentCodes, nextRootConcept, retVal, codeToConcept, 0); } return retVal; @@ -232,7 +219,15 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { * save parent concepts first (it's way too slow to do that) */ if (theConcept.getId() == null) { - retVal += ensureParentsSaved(theConcept.getParents()); + boolean needToSaveParents = false; + for (TermConceptParentChildLink next : theConcept.getParents()) { + if (next.getParent().getId() == null) { + needToSaveParents = true; + } + } + if (needToSaveParents) { + retVal += ensureParentsSaved(theConcept.getParents()); + } } if (theConcept.getId() == null || theConcept.getIndexStatus() == null) { @@ -296,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource); - ResourcePersistentId codeSystemResourcePid = myIdHelperService.translateForcedIdToPid(csId, theRequest); + ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(null, csId.getResourceType(), csId.getIdPart()); ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong()); ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); @@ -445,7 +440,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { Validate.isTrue(myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), "Terminology operations only supported in DSTU3+ mode"); } - private void addConcept(TermCodeSystemVersion theCsv, Collection theParentCodes, TermConcept theConceptToAdd, UploadStatistics theStatisticsTracker, boolean theRootConcept, int theSequence) { + private void addConceptInHierarchy(TermCodeSystemVersion theCsv, Collection theParentCodes, TermConcept theConceptToAdd, UploadStatistics theStatisticsTracker, Map theCodeToConcept, int theSequence) { TermConcept conceptToAdd = theConceptToAdd; List childrenToAdd = theConceptToAdd.getChildren(); @@ -470,15 +465,18 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { for (String nextParentCode : theParentCodes) { // Don't add parent links that already exist for the code - if (existingParentLinks.stream().anyMatch(t->t.getParent().getCode().equals(nextParentCode))) { + if (existingParentLinks.stream().anyMatch(t -> t.getParent().getCode().equals(nextParentCode))) { continue; } - Optional nextParentOpt = myConceptDao.findByCodeSystemAndCode(theCsv, nextParentCode); - if (nextParentOpt.isPresent() == false) { + TermConcept nextParentOpt = theCodeToConcept.get(nextParentCode); + if (nextParentOpt == null) { + nextParentOpt = myConceptDao.findByCodeSystemAndCode(theCsv, nextParentCode).orElse(null); + } + if (nextParentOpt == null) { throw new InvalidRequestException("Unable to add code \"" + nextCodeToAdd + "\" to unknown parent: " + nextParentCode); } - parentConceptsWeShouldLinkTo.add(nextParentOpt.get()); + parentConceptsWeShouldLinkTo.add(nextParentOpt); } if (conceptToAdd.getSequence() == null) { @@ -489,10 +487,17 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { // force a reindex, and it'll be regenerated then conceptToAdd.setParentPids(null); conceptToAdd.setCodeSystemVersion(theCsv); - conceptToAdd = myConceptDao.save(conceptToAdd); - Long nextConceptPid = conceptToAdd.getId(); - Validate.notNull(nextConceptPid); + if (theStatisticsTracker.getUpdatedConceptCount() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { + saveConcept(conceptToAdd); + Long nextConceptPid = conceptToAdd.getId(); + Validate.notNull(nextConceptPid); + } else { + myDeferredStorageSvc.addConceptToStorageQueue(conceptToAdd); + } + + theCodeToConcept.put(conceptToAdd.getCode(), conceptToAdd); + theStatisticsTracker.incrementUpdatedConceptCount(); // Add link to new child to the parent @@ -505,7 +510,13 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { nextParentConcept.getChildren().add(parentLink); conceptToAdd.getParents().add(parentLink); ourLog.info("Saving parent/child link - Parent[{}] Child[{}]", parentLink.getParent().getCode(), parentLink.getChild().getCode()); - myConceptParentChildLinkDao.save(parentLink); + + if (theStatisticsTracker.getUpdatedConceptCount() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { + myConceptParentChildLinkDao.save(parentLink); + } else { + myDeferredStorageSvc.addConceptLinkToStorageQueue(parentLink); + } + } ourLog.trace("About to save parent-child links"); @@ -519,13 +530,20 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { for (int i = 0; i < nextChild.getParents().size(); i++) { if (nextChild.getParents().get(i).getId() == null) { String parentCode = nextChild.getParents().get(i).getParent().getCode(); - TermConcept parentConcept = myConceptDao.findByCodeSystemAndCode(theCsv, parentCode).orElseThrow(() -> new IllegalArgumentException("Unknown parent code: " + parentCode)); + TermConcept parentConcept = theCodeToConcept.get(parentCode); + if (parentConcept == null) { + parentConcept = myConceptDao.findByCodeSystemAndCode(theCsv, parentCode).orElse(null); + } + if (parentConcept == null) { + throw new IllegalArgumentException("Unknown parent code: " + parentCode); + } + nextChild.getParents().get(i).setParent(parentConcept); } } Collection parentCodes = nextChild.getParents().stream().map(t -> t.getParent().getCode()).collect(Collectors.toList()); - addConcept(theCsv, parentCodes, nextChild, theStatisticsTracker, false, childIndex); + addConceptInHierarchy(theCsv, parentCodes, nextChild, theStatisticsTracker, theCodeToConcept, childIndex); childIndex++; } @@ -533,11 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { } private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) { - return getCodeSystemResourcePid(theIdType, null); - } - - private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { - return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); + return myIdHelperService.resolveResourcePersistentIds(null, theIdType.getResourceType(), theIdType.getIdPart()); } private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack, int theTotalConcepts) { @@ -641,17 +655,20 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { private void deleteConceptChildrenAndConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) { for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) { deleteConceptChildrenAndConcept(nextChildLink.getChild(), theRemoveCounter); - myConceptParentChildLinkDao.delete(nextChildLink); } + myConceptParentChildLinkDao.deleteByConceptPid(theConcept.getId()); + myConceptDesignationDao.deleteAll(theConcept.getDesignations()); myConceptPropertyDao.deleteAll(theConcept.getProperties()); - myConceptDao.delete(theConcept); + + ourLog.info("Deleting concept {} - Code {}", theConcept.getId(), theConcept.getCode()); + myConceptDao.deleteByPid(theConcept.getId()); theRemoveCounter.incrementAndGet(); } - private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { + private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, IHapiJpaRepository theDao) { int count; ourLog.info(" * Deleting {}", theDescriptor); int totalCount = theCounter.get(); @@ -666,7 +683,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.execute(t -> { - link.forEach(id -> theDao.deleteById(id)); + link.forEach(id -> theDao.deleteByPid(id)); return null; }); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java index e262ef1cedb..17c66795c59 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -98,6 +98,13 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc { myDeferredValueSets.addAll(theValueSets); } + @Override + public void saveAllDeferred() { + while (!isStorageQueueEmpty()) { + saveDeferred(); + } + } + @Override public void setProcessDeferred(boolean theProcessDeferred) { myProcessDeferred = theProcessDeferred; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java index 3dcab1b5a24..541dd070b36 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java @@ -20,13 +20,16 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; @@ -62,11 +65,6 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { return false; } - @Override - public CodeSystem getCodeSystemFromContext(String theSystem) { - return null; - } - @Override protected ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) { throw new UnsupportedOperationException(); @@ -78,22 +76,17 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { } @Override - public IBaseResource expandValueSet(IBaseResource theValueSetToExpand) { + protected CodeSystem toCanonicalCodeSystem(IBaseResource theCodeSystem) { throw new UnsupportedOperationException(); } @Override - public IBaseResource expandValueSet(IBaseResource theValueSetToExpand, int theOffset, int theCount) { + public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { throw new UnsupportedOperationException(); } @Override - public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { - throw new UnsupportedOperationException(); - } - - @Override - public List expandValueSet(String theValueSet) { + public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { throw new UnsupportedOperationException(); } @@ -107,7 +100,7 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { @Override public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { ArrayList retVal = new ArrayList<>(); - org.hl7.fhir.dstu2.model.ValueSet system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + org.hl7.fhir.dstu2.model.ValueSet system = (org.hl7.fhir.dstu2.model.ValueSet) myValidationSupport.fetchCodeSystem(theSystem); if (system != null) { findCodesAbove(system, theSystem, theCode, retVal); } @@ -132,7 +125,7 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { @Override public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { ArrayList retVal = new ArrayList<>(); - org.hl7.fhir.dstu2.model.ValueSet system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + org.hl7.fhir.dstu2.model.ValueSet system = (org.hl7.fhir.dstu2.model.ValueSet) myValidationSupport.fetchCodeSystem(theSystem); if (system != null) { findCodesBelow(system, theSystem, theCode, retVal); } @@ -140,7 +133,7 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { } @Override - public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { + public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { throw new UnsupportedOperationException(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java index aee82c3ce8c..5fe1ee1f929 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java @@ -1,42 +1,34 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ValidateUtil; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.util.VersionIndependentConcept; +import org.hl7.fhir.convertors.conv30_40.CodeSystem30_40; import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Optional; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.hl7.fhir.convertors.conv30_40.CodeSystem30_40.convertCodeSystem; import static org.hl7.fhir.convertors.conv30_40.ValueSet30_40.convertValueSet; -import static org.hl7.fhir.convertors.conv30_40.ValueSet30_40.convertValueSetExpansionComponent; /* * #%L @@ -60,13 +52,6 @@ import static org.hl7.fhir.convertors.conv30_40.ValueSet30_40.convertValueSetExp public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidationSupport, ITermReadSvcDstu3 { - @Autowired - @Qualifier("myValueSetDaoDstu3") - private IFhirResourceDao myValueSetResourceDao; - @Autowired - private IValidationSupport myValidationSupport; - @Autowired - private ITermReadSvc myTerminologySvc; @Autowired private PlatformTransactionManager myTransactionManager; @@ -77,53 +62,28 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation super(); } - private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { - if (isNotBlank(theCode.getCode())) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); - } - for (ConceptDefinitionComponent nextChild : theCode.getConcept()) { - addAllChildren(theSystemString, nextChild, theListToPopulate); - } - } - - private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { - boolean foundCodeInChild = false; - for (ConceptDefinitionComponent nextChild : theNext.getConcept()) { - foundCodeInChild |= addTreeIfItContainsCode(theSystemString, nextChild, theCode, theListToPopulate); - } - - if (theCode.equals(theNext.getCode()) || foundCodeInChild) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theNext.getCode())); - return true; - } - - return false; - } @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSet valueSetToExpand = new ValueSet(); - valueSetToExpand.getCompose().addInclude(theInclude); - + public ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { try { org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; - valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand); - org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null).getExpansion(); - return convertValueSetExpansionComponent(expandedR4); + valueSetToExpandR4 = toCanonicalValueSet(theValueSetToExpand); + org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(theExpansionOptions, valueSetToExpandR4); + return new ValueSetExpansionOutcome(convertValueSet(expandedR4), null); } catch (FHIRException e) { throw new InternalErrorException(e); } } @Override - public IBaseResource expandValueSet(IBaseResource theInput) { + public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theInput) { ValueSet valueSetToExpand = (ValueSet) theInput; try { org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand); - org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null); + org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(theExpansionOptions, valueSetToExpandR4); return convertValueSet(expandedR4); } catch (FHIRException e) { throw new InternalErrorException(e); @@ -138,131 +98,18 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation } @Override - public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) { - ValueSet valueSetToExpand = (ValueSet) theInput; - - try { - org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; - valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand); - org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(valueSetToExpandR4, theOffset, theCount); - return convertValueSet(expandedR4); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } + protected org.hl7.fhir.r4.model.CodeSystem toCanonicalCodeSystem(IBaseResource theCodeSystem) { + return CodeSystem30_40.convertCodeSystem((CodeSystem)theCodeSystem); } @Override - public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { + public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; try { org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand); - super.expandValueSet(valueSetToExpandR4, theValueSetCodeAccumulator); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - @Override - public List expandValueSet(String theValueSet) { - // TODO: DM 2019-09-10 - This is problematic because an incorrect URL that matches ValueSet.id will not be found in the terminology tables but will yield a ValueSet here. Depending on the ValueSet, the expansion may time-out. - ValueSet vs = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet); - if (vs == null) { - super.throwInvalidValueSet(theValueSet); - } - - org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; - try { - valueSetToExpandR4 = toCanonicalValueSet(vs); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - - return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4, null); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @CoverageIgnore - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public IBaseResource fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @CoverageIgnore - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return null; - } - - @CoverageIgnore - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - for (ConceptDefinitionComponent next : conceptList) { - addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); - } - } - - @Override - public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesAbove(system, theSystem, theCode, retVal); - } - return retVal; - } - - private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); - } - - private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { - for (ConceptDefinitionComponent next : conceptList) { - if (theCode.equals(next.getCode())) { - addAllChildren(theSystemString, next, theListToPopulate); - } else { - findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); - } - } - } - - @Override - public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesBelow(system, theSystem, theCode, retVal); - } - return retVal; - } - - @Override - public org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { - CodeSystem codeSystem = myValidationSupport.fetchCodeSystem(myContext, theSystem); - try { - return convertCodeSystem(codeSystem); + super.expandValueSet(theExpansionOptions, valueSetToExpandR4, theValueSetCodeAccumulator); } catch (FHIRException e) { throw new InternalErrorException(e); } @@ -270,7 +117,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation @Override protected org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) { - ValueSet valueSet = myValueSetResourceDao.toResource(ValueSet.class, theResourceTable, null, false); + ValueSet valueSet = myDaoRegistry.getResourceDao("ValueSet").toResource(ValueSet.class, theResourceTable, null, false); org.hl7.fhir.r4.model.ValueSet valueSetR4; try { @@ -282,51 +129,47 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation return valueSetR4; } - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return myTerminologySvc.supportsSystem(theSystem); - } - @CoverageIgnore @Override - public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + public IValidationSupport.CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { Optional codeOpt = Optional.empty(); boolean haveValidated = false; if (isNotBlank(theValueSetUrl)) { - codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode); + codeOpt = super.validateCodeInValueSet(theRootValidationSupport, theOptions, theValueSetUrl, theCodeSystem, theCode); haveValidated = true; } if (!haveValidated) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c->c.toVersionIndependentConcept())); + codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); } if (codeOpt != null && codeOpt.isPresent()) { VersionIndependentConcept code = codeOpt.get(); - ConceptDefinitionComponent def = new ConceptDefinitionComponent(); - def.setCode(code.getCode()); - IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def); + IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult() + .setCode(code.getCode()); return retVal; } - return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode); + return new IValidationSupport.CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setMessage("Unknown code {" + theCodeSystem + "}" + theCode); } @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return super.lookupCode(theContext, theSystem, theCode); + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return super.lookupCode(theSystem, theCode); } @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; + public FhirContext getFhirContext() { + return myContext; } @Override - public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { + public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValueSet valueSet = (ValueSet) theValueSet; org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet); @@ -346,7 +189,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation } } - return super.validateCodeIsInPreExpandedValueSet(valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); + return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java index 874404d001a..103a0ef089f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java @@ -1,30 +1,28 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; import javax.transaction.Transactional; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Optional; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -51,189 +49,62 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4 { - @Autowired - @Qualifier("myValueSetDaoR4") - private IFhirResourceDao myValueSetResourceDao; - @Autowired - private IValidationSupport myValidationSupport; @Autowired private PlatformTransactionManager myTransactionManager; - private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { - if (isNotBlank(theCode.getCode())) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); - } - for (ConceptDefinitionComponent nextChild : theCode.getConcept()) { - addAllChildren(theSystemString, nextChild, theListToPopulate); - } - } - - private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { - boolean foundCodeInChild = false; - for (ConceptDefinitionComponent nextChild : theNext.getConcept()) { - foundCodeInChild |= addTreeIfItContainsCode(theSystemString, nextChild, theCode, theListToPopulate); - } - - if (theCode.equals(theNext.getCode()) || foundCodeInChild) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theNext.getCode())); - return true; - } - - return false; - } - - @Override - public List expandValueSet(String theValueSet) { - // TODO: DM 2019-09-10 - This is problematic because an incorrect URL that matches ValueSet.id will not be found in the terminology tables but will yield a ValueSet here. Depending on the ValueSet, the expansion may time-out. - ValueSet vs = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet); - if (vs == null) { - super.throwInvalidValueSet(theValueSet); - } - - return expandValueSetAndReturnVersionIndependentConcepts(vs, null); - } - - @Override - public IBaseResource expandValueSet(IBaseResource theInput) { + public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theInput) { ValueSet valueSetToExpand = (ValueSet) theInput; - return super.expandValueSetInMemory(valueSetToExpand, null); + return super.expandValueSet(theExpansionOptions, valueSetToExpand); } @Override - public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) { - ValueSet valueSetToExpand = (ValueSet) theInput; - return super.expandValueSet(valueSetToExpand, theOffset, theCount); - } - - @Override - public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { + public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; - super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator); + super.expandValueSet(theExpansionOptions, valueSetToExpand, theValueSetCodeAccumulator); } @Transactional(dontRollbackOn = {ExpansionTooCostlyException.class}) @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSet valueSetToExpand = new ValueSet(); - valueSetToExpand.getCompose().addInclude(theInclude); - ValueSet expanded = super.expandValueSetInMemory(valueSetToExpand, null); - return new ValueSetExpander.ValueSetExpansionOutcome(expanded); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @CoverageIgnore - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @CoverageIgnore - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @CoverageIgnore - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - for (ConceptDefinitionComponent next : conceptList) { - addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); - } - } - - @Override - public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesAbove(system, theSystem, theCode, retVal); - } - return retVal; - } - - private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); - } - - private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { - for (ConceptDefinitionComponent next : conceptList) { - if (theCode.equals(next.getCode())) { - addAllChildren(theSystemString, next, theListToPopulate); - } else { - findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); - } - } - } - - @Override - public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesBelow(system, theSystem, theCode, retVal); - } - return retVal; - } - - @Override - public CodeSystem getCodeSystemFromContext(String theSystem) { - return myValidationSupport.fetchCodeSystem(myContext, theSystem); + public IValidationSupport.ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { + ValueSet expanded = super.expandValueSet(theExpansionOptions, (ValueSet) theValueSetToExpand); + return new IValidationSupport.ValueSetExpansionOutcome(expanded); } @Override protected ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) { - return myValueSetResourceDao.toResource(ValueSet.class, theResourceTable, null, false); + return myDaoRegistry.getResourceDao("ValueSet").toResource(ValueSet.class, theResourceTable, null, false); } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return supportsSystem(theSystem); + public boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + return fetchValueSet(theValueSetUrl) != null; } @Override - public boolean isValueSetSupported(FhirContext theContext, String theValueSetUrl) { - return myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSetUrl) != null; + public FhirContext getFhirContext() { + return myContext; } - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } @Override protected ValueSet toCanonicalValueSet(IBaseResource theValueSet) { return (ValueSet) theValueSet; } + @Override + protected CodeSystem toCanonicalCodeSystem(IBaseResource theCodeSystem) { + return (CodeSystem) theCodeSystem; + } + @CoverageIgnore @Override - public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + public IValidationSupport.CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { Optional codeOpt = Optional.empty(); boolean haveValidated = false; if (isNotBlank(theValueSetUrl)) { - codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode); + codeOpt = super.validateCodeInValueSet(theRootValidationSupport, theOptions, theValueSetUrl, theCodeSystem, theCode); haveValidated = true; } @@ -245,26 +116,27 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4 if (codeOpt != null && codeOpt.isPresent()) { VersionIndependentConcept code = codeOpt.get(); - ConceptDefinitionComponent def = new ConceptDefinitionComponent(); - def.setCode(code.getCode()); - IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def); + IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult() + .setCode(code.getCode()); // AAAAAAAAAAA format return retVal; } - return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode); + return new IValidationSupport.CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setMessage("Unknown code {" + theCodeSystem + "}" + theCode); } @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return super.lookupCode(theContext, theSystem, theCode); + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return super.lookupCode(theSystem, theCode); } @Override - public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { + public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { ValueSet valueSet = (ValueSet) theValueSet; Coding coding = (Coding) theCoding; CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept; - return super.validateCodeIsInPreExpandedValueSet(valueSet, theSystem, theCode, theDisplay, coding, codeableConcept); + return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSet, theSystem, theCode, theDisplay, coding, codeableConcept); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java index a4f8ab5136b..12362e34a06 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java @@ -1,29 +1,32 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; -import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.util.ValidateUtil; +import ca.uhn.fhir.util.VersionIndependentConcept; +import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.utilities.TerminologyServiceOptions; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import javax.transaction.Transactional; import java.util.Optional; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -51,208 +54,79 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSupport, ITermReadSvcR5 { @Autowired - @Qualifier("myValueSetDaoR5") - private IFhirResourceDao myValueSetResourceDao; - @Autowired - private IValidationSupport myValidationSupport; + private DaoRegistry myDaoRegistry; @Autowired private PlatformTransactionManager myTransactionManager; - private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { - if (isNotBlank(theCode.getCode())) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); - } - for (ConceptDefinitionComponent nextChild : theCode.getConcept()) { - addAllChildren(theSystemString, nextChild, theListToPopulate); - } - } - - private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { - boolean foundCodeInChild = false; - for (ConceptDefinitionComponent nextChild : theNext.getConcept()) { - foundCodeInChild |= addTreeIfItContainsCode(theSystemString, nextChild, theCode, theListToPopulate); - } - - if (theCode.equals(theNext.getCode()) || foundCodeInChild) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theNext.getCode())); - return true; - } - - return false; - } - - @Override - public List expandValueSet(String theValueSet) { - // TODO: DM 2019-09-10 - This is problematic because an incorrect URL that matches ValueSet.id will not be found in the terminology tables but will yield a ValueSet here. Depending on the ValueSet, the expansion may time-out. - ValueSet valueSetR5 = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet); - if (valueSetR5 == null) { - super.throwInvalidValueSet(theValueSet); - } - - return expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5), null); + @Transactional(dontRollbackOn = {ExpansionTooCostlyException.class}) + public ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { + ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; + org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(theExpansionOptions, org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetToExpand)); + return new ValueSetExpansionOutcome(org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(expandedR4)); } @Override - public IBaseResource expandValueSet(IBaseResource theInput) { + public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theInput) { org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theInput); - org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSetInMemory(valueSetToExpand, null); + org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(theExpansionOptions, valueSetToExpand); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR4); } @Override - public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) { - org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theInput); - org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(valueSetToExpand, theOffset, theCount); - return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR4); - } - - @Override - public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { + public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theValueSetToExpand); - super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSet valueSetToExpand = new ValueSet(); - valueSetToExpand.getCompose().addInclude(theInclude); - org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSetInMemory(org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetToExpand), null); - return new ValueSetExpander.ValueSetExpansionOutcome(org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(expandedR4)); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - - @CoverageIgnore - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return null; - } - - @CoverageIgnore - @Override - public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @CoverageIgnore - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - for (ConceptDefinitionComponent next : conceptList) { - addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); - } - } - - @Override - public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesAbove(system, theSystem, theCode, retVal); - } - return retVal; - } - - private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); - } - - private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { - for (ConceptDefinitionComponent next : conceptList) { - if (theCode.equals(next.getCode())) { - addAllChildren(theSystemString, next, theListToPopulate); - } else { - findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); - } - } - } - - @Override - public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesBelow(system, theSystem, theCode, retVal); - } - return retVal; - } - - @Override - public org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { - CodeSystem codeSystemR5 = myValidationSupport.fetchCodeSystem(myContext, theSystem); - return org.hl7.fhir.convertors.conv40_50.CodeSystem40_50.convertCodeSystem(codeSystemR5); + super.expandValueSet(theExpansionOptions, valueSetToExpand, theValueSetCodeAccumulator); } @Override protected org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) { - ValueSet valueSetR5 = myValueSetResourceDao.toResource(ValueSet.class, theResourceTable, null, false); + ValueSet valueSetR5 = myDaoRegistry.getResourceDao("ValueSet").toResource(ValueSet.class, theResourceTable, null, false); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5); } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return supportsSystem(theSystem); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - @CoverageIgnore - @Override - public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + public IValidationSupport.CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { Optional codeOpt = Optional.empty(); boolean haveValidated = false; if (isNotBlank(theValueSetUrl)) { - codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode); + codeOpt = super.validateCodeInValueSet(theRootValidationSupport, theOptions, theValueSetUrl, theCodeSystem, theCode); haveValidated = true; } if (!haveValidated) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c->c.toVersionIndependentConcept())); + codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> c.toVersionIndependentConcept())); } if (codeOpt != null && codeOpt.isPresent()) { VersionIndependentConcept code = codeOpt.get(); ConceptDefinitionComponent def = new ConceptDefinitionComponent(); def.setCode(code.getCode()); - IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def); + IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult() + .setCode(code.getCode()); return retVal; } - return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode); + return new IValidationSupport.CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setCode("Unknown code {" + theCodeSystem + "}" + theCode); } @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return super.lookupCode(theContext, theSystem, theCode); + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return super.lookupCode(theSystem, theCode); } @Override - public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { + public FhirContext getFhirContext() { + return myContext; + } + + @Override + public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValueSet valueSet = (ValueSet) theValueSet; org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet); @@ -272,7 +146,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup } } - return super.validateCodeIsInPreExpandedValueSet(valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); + return super.validateCodeIsInPreExpandedValueSet(new TerminologyServiceOptions(), valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); } @Override @@ -280,6 +154,11 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet((ValueSet) theValueSet); } + @Override + protected org.hl7.fhir.r4.model.CodeSystem toCanonicalCodeSystem(IBaseResource theCodeSystem) { + return CodeSystem40_50.convertCodeSystem((CodeSystem) theCodeSystem); + } + @Override public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java index 5fce60cfa30..830de39433e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java @@ -20,11 +20,10 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.UrlUtil; -import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.dstu3.model.ValueSet; @@ -33,7 +32,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.ContextStartedEvent; import org.springframework.context.event.EventListener; import static org.apache.commons.lang3.StringUtils.isBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java index bfa30350106..b3cb8a75e62 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.util.UrlUtil; import org.hl7.fhir.instance.model.api.IIdType; @@ -28,13 +28,10 @@ import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; -import javax.annotation.PostConstruct; - import static org.apache.commons.lang3.StringUtils.isBlank; public class TermVersionAdapterSvcR4 extends BaseTermVersionAdapterSvcImpl implements ITermVersionAdapterSvc { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java index 83cc9c0ae76..e4ae628e80f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term; * #L% */ -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.util.UrlUtil; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java index 881ce14e7f6..2aff45f7fef 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java @@ -50,4 +50,9 @@ public interface ITermDeferredStorageSvc { void addConceptMapsToStorageQueue(List theConceptMaps); void addValueSetsToStorageQueue(List theValueSets); + + /** + * This is mostly here for unit tests - Saves any and all deferred concepts and links + */ + void saveAllDeferred(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 2182a63e2f7..43973d6a046 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -1,14 +1,16 @@ package ca.uhn.fhir.jpa.term.api; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -16,7 +18,9 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.utilities.validation.ValidationOptions; +import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.Set; @@ -51,27 +55,20 @@ import java.util.Set; * been moved yet) *

        */ -public interface ITermReadSvc { +public interface ITermReadSvc extends IValidationSupport { - ValueSet expandValueSetInMemory(ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull); + ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand); - ValueSet expandValueSet(ValueSet theValueSetToExpand, int theOffset, int theCount); - - void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator); + void expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator); /** * Version independent */ - IBaseResource expandValueSet(IBaseResource theValueSetToExpand); + IBaseResource expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand); - /** - * Version independent - */ - IBaseResource expandValueSet(IBaseResource theValueSetToExpand, int theOffset, int theCount); + void expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator); - void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator); - - List expandValueSet(String theValueSet); + List expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet); Optional findCode(String theCodeSystem, String theCode); @@ -87,7 +84,7 @@ public interface ITermReadSvc { List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode); - CodeSystem getCodeSystemFromContext(String theSystem); + CodeSystem fetchCanonicalCodeSystemFromCompleteContext(String theSystem); void deleteConceptMapAndChildren(ResourceTable theResourceTable); @@ -110,7 +107,7 @@ public interface ITermReadSvc { /** * Version independent */ - ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept); + IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept); boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java index a9bb95378b1..2d841e567f9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term.api; * #L% */ -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; public interface ITermReadSvcDstu3 extends ITermReadSvc, IValidationSupport { // nothing diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java index aea8aa232df..a8415694c7a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term.api; * #L% */ -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; public interface ITermReadSvcR4 extends ITermReadSvc, IValidationSupport { // nothing diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java index b1477acc622..98e950e2738 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.term.api; * #L% */ -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; public interface ITermReadSvcR5 extends ITermReadSvc, IValidationSupport { // nothing diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java index 3a6cf79afca..7b74ffb7c17 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.util; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.dstu2.resource.Subscription; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java index 74f54a4dc4d..23b74d674d6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.util; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java index 4d17291a501..5983b3d787f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.util; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java index 82a9fb4277e..c038fdb1399 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java @@ -28,6 +28,7 @@ import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ClassInfo; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; +import org.hibernate.annotations.Subselect; import org.hibernate.validator.constraints.Length; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.InstantType; @@ -39,7 +40,11 @@ import java.io.InputStream; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Collectors; import static com.google.common.base.Ascii.toUpperCase; @@ -96,7 +101,10 @@ public class TestUtil { private static void scanClass(Set theNames, Class theClazz, boolean theIsSuperClass) { ourLog.info("Scanning: {}", theClazz.getSimpleName()); - scan(theClazz, theNames, theIsSuperClass); + Subselect subselect = theClazz.getAnnotation(Subselect.class); + boolean isView = (subselect != null); + + scan(theClazz, theNames, theIsSuperClass, isView); for (Field nextField : theClazz.getDeclaredFields()) { if (Modifier.isStatic(nextField.getModifiers())) { @@ -104,7 +112,7 @@ public class TestUtil { } ourLog.info(" * Scanning field: {}", nextField.getName()); - scan(nextField, theNames, theIsSuperClass); + scan(nextField, theNames, theIsSuperClass, isView); Lob lobClass = nextField.getAnnotation(Lob.class); if (lobClass != null) { @@ -118,11 +126,13 @@ public class TestUtil { boolean hasColumn = nextField.getAnnotation(Column.class) != null; boolean hasJoinColumn = nextField.getAnnotation(JoinColumn.class) != null; boolean hasEmbeddedId = nextField.getAnnotation(EmbeddedId.class) != null; + boolean hasEmbedded = nextField.getAnnotation(Embedded.class) != null; OneToMany oneToMany = nextField.getAnnotation(OneToMany.class); OneToOne oneToOne = nextField.getAnnotation(OneToOne.class); boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy()); boolean isOtherSideOfOneToOneMapping = oneToOne != null && isNotBlank(oneToOne.mappedBy()); Validate.isTrue( + hasEmbedded || hasColumn || hasJoinColumn || isOtherSideOfOneToManyMapping || @@ -140,7 +150,7 @@ public class TestUtil { scanClass(theNames, theClazz.getSuperclass(), true); } - private static void scan(AnnotatedElement theAnnotatedElement, Set theNames, boolean theIsSuperClass) { + private static void scan(AnnotatedElement theAnnotatedElement, Set theNames, boolean theIsSuperClass, boolean theIsView) { Table table = theAnnotatedElement.getAnnotation(Table.class); if (table != null) { @@ -198,7 +208,7 @@ public class TestUtil { */ if (field.getType().equals(String.class)) { if (!hasLob) { - if (column.length() == 255) { + if (!theIsView && column.length() == 255) { throw new IllegalStateException("Field does not have an explicit maximum length specified: " + field); } if (column.length() > MAX_COL_LENGTH) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java index c03507ec756..1716f0976d1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java @@ -1,12 +1,14 @@ package ca.uhn.fhir.jpa.util.xmlpatch; -import java.io.*; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import com.github.dnault.xmlpatch.Patcher; import org.hl7.fhir.instance.model.api.IBaseResource; -import com.github.dnault.xmlpatch.Patcher; - -import ca.uhn.fhir.context.FhirContext; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; /* * #%L @@ -28,9 +30,6 @@ import ca.uhn.fhir.context.FhirContext; * #L% */ -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; - public class XmlPatchUtils { public static T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java new file mode 100644 index 00000000000..49ffcacb283 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java @@ -0,0 +1,74 @@ +package ca.uhn.fhir.jpa.validation; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +public class JpaValidationSupportChain extends ValidationSupportChain { + + private final FhirContext myFhirContext; + + @Autowired + @Qualifier("myJpaValidationSupport") + public IValidationSupport myJpaValidationSupport; + + @Qualifier("myDefaultProfileValidationSupport") + @Autowired + private IValidationSupport myDefaultProfileValidationSupport; + @Autowired + private ITermReadSvc myTerminologyService; + + public JpaValidationSupportChain(FhirContext theFhirContext) { + myFhirContext = theFhirContext; + } + + @Override + public FhirContext getFhirContext() { + return myFhirContext; + } + + @PreDestroy + public void flush() { + invalidateCaches(); + } + + @PostConstruct + public void postConstruct() { + addValidationSupport((IValidationSupport) new CommonCodeSystemsTerminologyService(myFhirContext)); + addValidationSupport(myDefaultProfileValidationSupport); + addValidationSupport(myJpaValidationSupport); + addValidationSupport((IValidationSupport) myTerminologyService); + addValidationSupport((IValidationSupport) new SnapshotGeneratingValidationSupport(myFhirContext)); + addValidationSupport((IValidationSupport) new InMemoryTerminologyServerValidationSupport(myFhirContext)); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java deleted file mode 100644 index 6f96d9be239..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java +++ /dev/null @@ -1,88 +0,0 @@ -package ca.uhn.fhir.jpa.validation; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.dstu3.hapi.validation.SnapshotGeneratingValidationSupport; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -public class JpaValidationSupportChainDstu3 extends ValidationSupportChain { - - @Autowired - @Qualifier("myJpaValidationSupportDstu3") - public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 myJpaValidationSupportDstu3; - @Autowired - private DefaultProfileValidationSupport myDefaultProfileValidationSupport; - @Autowired - private ITermReadSvcDstu3 myTerminologyService; - @Autowired - private FhirContext myFhirContext; - - public JpaValidationSupportChainDstu3() { - super(); - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - return super.fetchResource(theContext, theClass, theUri); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - StructureDefinition retVal = super.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null && !retVal.hasSnapshot()) { - retVal = generateSnapshot(retVal, theUrl, null); - } - return retVal; - } - - public void flush() { - myDefaultProfileValidationSupport.flush(); - } - - @PostConstruct - public void postConstruct() { - addValidationSupport(myDefaultProfileValidationSupport); - addValidationSupport(myJpaValidationSupportDstu3); - addValidationSupport(myTerminologyService); - addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this)); - } - - @PreDestroy - public void preDestroy() { - flush(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java deleted file mode 100644 index 9800cc0e285..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java +++ /dev/null @@ -1,92 +0,0 @@ -package ca.uhn.fhir.jpa.validation; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.validation.SnapshotGeneratingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -public class JpaValidationSupportChainR4 extends ValidationSupportChain { - - @Autowired - private DefaultProfileValidationSupport myDefaultProfileValidationSupport; - - @Autowired - private FhirContext myFhirContext; - - @Autowired - @Qualifier("myJpaValidationSupportR4") - public ca.uhn.fhir.jpa.dao.r4.IJpaValidationSupportR4 myJpaValidationSupportR4; - - @Autowired - private ITermReadSvcR4 myTerminologyService; - - public JpaValidationSupportChainR4() { - super(); - } - - public void flush() { - myDefaultProfileValidationSupport.flush(); - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - return super.fetchResource(theContext, theClass, theUri); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - StructureDefinition retVal = super.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null && !retVal.hasSnapshot()) { - retVal = generateSnapshot(retVal, theUrl, null, null); - } - return retVal; - } - - - @PostConstruct - public void postConstruct() { - addValidationSupport(myDefaultProfileValidationSupport); - addValidationSupport(myJpaValidationSupportR4); - addValidationSupport(myTerminologyService); - addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this)); - } - - @PreDestroy - public void preDestroy() { - flush(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java deleted file mode 100644 index 6ffe80185a7..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java +++ /dev/null @@ -1,87 +0,0 @@ -package ca.uhn.fhir.jpa.validation; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.validation.SnapshotGeneratingValidationSupport; -import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -public class JpaValidationSupportChainR5 extends ValidationSupportChain { - - @Autowired - private DefaultProfileValidationSupport myDefaultProfileValidationSupport; - - @Autowired - private FhirContext myFhirContext; - - @Autowired - @Qualifier("myJpaValidationSupportR5") - public ca.uhn.fhir.jpa.dao.r5.IJpaValidationSupportR5 myJpaValidationSupportR5; - - @Autowired - private ITermReadSvcR5 myTerminologyService; - - public JpaValidationSupportChainR5() { - super(); - } - - @PreDestroy - public void flush() { - myDefaultProfileValidationSupport.flush(); - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - return super.fetchResource(theContext, theClass, theUri); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - StructureDefinition retVal = super.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null && !retVal.hasSnapshot()) { - retVal = generateSnapshot(retVal, theUrl, null, null); - } - return retVal; - } - - - @PostConstruct - public void postConstruct() { - addValidationSupport(myDefaultProfileValidationSupport); - addValidationSupport(myJpaValidationSupportR5); - addValidationSupport(myTerminologyService); - addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this)); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java index 436627a67b2..34e835f5e83 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java @@ -9,7 +9,6 @@ import ca.uhn.fhir.jpa.entity.BulkExportCollectionFileEntity; import ca.uhn.fhir.jpa.entity.BulkExportJobEntity; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import com.google.common.base.Charsets; import com.google.common.collect.Sets; import org.apache.commons.lang3.time.DateUtils; @@ -23,7 +22,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.TestPropertySource; import java.util.Date; import java.util.UUID; @@ -34,6 +32,7 @@ import static org.junit.Assert.*; public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(BulkDataExportSvcImplR4Test.class); + public static final String TEST_FILTER = "Patient?gender=female"; @Autowired private IBulkExportJobDao myBulkExportJobDao; @Autowired @@ -134,13 +133,13 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { createResources(); // Create a bulk job - IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(null, Sets.newHashSet("Patient", "Observation"), null, null); + IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(null, Sets.newHashSet("Patient", "Observation"), null, Sets.newHashSet(TEST_FILTER)); assertNotNull(jobDetails.getJobId()); // Check the status IBulkDataExportSvc.JobInfo status = myBulkDataExportSvc.getJobStatusOrThrowResourceNotFound(jobDetails.getJobId()); assertEquals(BulkJobStatusEnum.SUBMITTED, status.getStatus()); - assertEquals("/$export?_outputFormat=application%2Ffhir%2Bndjson&_type=Observation,Patient", status.getRequest()); + assertEquals("/$export?_outputFormat=application%2Ffhir%2Bndjson&_type=Observation,Patient&_typeFilter="+TEST_FILTER, status.getRequest()); // Run a scheduled pass to build the export myBulkDataExportSvc.buildExportFiles(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java index d4603c46202..1368c1cc509 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.config; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.Message; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index fb81ca3896f..92dd30de97b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -157,7 +157,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorDstu2()); + requestValidator.addValidatorModule(instanceValidator()); return requestValidator; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index 87469cab62e..aa20363697d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; @@ -157,7 +157,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorDstu3()); + requestValidator.addValidatorModule(instanceValidator()); return requestValidator; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java index aa2457b48a7..d8f536c782c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java @@ -1,26 +1,38 @@ package ca.uhn.fhir.jpa.config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; +import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; -import org.springframework.core.env.Environment; import org.springframework.orm.jpa.JpaTransactionManager; import javax.persistence.EntityManagerFactory; @Configuration +@Import({ + SubscriptionSubmitterConfig.class, + SubscriptionProcessorConfig.class, + SubscriptionChannelConfig.class +}) public class TestJPAConfig { @Bean public DaoConfig daoConfig() { - DaoConfig daoConfig = new DaoConfig(); - return daoConfig; + return new DaoConfig(); + } + + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); } @Bean diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index c7da23d9005..3955361d5fd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -163,7 +163,7 @@ public class TestR4Config extends BaseJavaConfigR4 { requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorR4()); + requestValidator.addValidatorModule(instanceValidator()); return requestValidator; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR5Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR5Config.java index 637ef1a7342..90a359ea404 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR5Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR5Config.java @@ -159,7 +159,7 @@ public class TestR5Config extends BaseJavaConfigR5 { requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorR5()); + requestValidator.addValidatorModule(instanceValidator()); return requestValidator; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 14b1779dd9c..f1a87ee772c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -4,21 +4,24 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; -import ca.uhn.fhir.jpa.util.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.rest.api.Constants; @@ -106,6 +109,8 @@ public abstract class BaseJpaTest extends BaseTest { protected ISearchResultCacheSvc mySearchResultCacheSvc; @Autowired protected ISearchCacheSvc mySearchCacheSvc; + @Autowired + protected IPartitionLookupSvc myPartitionConfigSvc; @After public void afterPerformCleanup() { @@ -113,6 +118,9 @@ public abstract class BaseJpaTest extends BaseTest { if (myCaptureQueriesListener != null) { myCaptureQueriesListener.clear(); } + if (myPartitionConfigSvc != null) { + myPartitionConfigSvc.clearCaches(); + } } @After @@ -142,6 +150,13 @@ public abstract class BaseJpaTest extends BaseTest { } } + @Before + public void beforeInitPartitions() { + if (myPartitionConfigSvc != null) { + myPartitionConfigSvc.start(); + } + } + @Before public void beforeInitMocks() { myRequestOperationCallback = new InterceptorService(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/DaoConfigTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/DaoConfigTest.java index 2c4b6e3b58c..e5f3819583b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/DaoConfigTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/DaoConfigTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import org.junit.Test; import java.util.Arrays; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java index 9915a2cddb0..c7e3a79de5b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.dao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import org.junit.Test; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java new file mode 100644 index 00000000000..dea758dc150 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/TolerantJsonParserR4Test.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.LenientErrorHandler; +import org.hl7.fhir.r4.model.Observation; +import org.junit.Test; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class TolerantJsonParserR4Test { + + private FhirContext myFhirContext = FhirContext.forR4(); + + @Test + public void testParseInvalidNumeric_LeadingDecimal() { + String input = "{\n" + + "\"resourceType\": \"Observation\",\n" + + "\"valueQuantity\": {\n" + + " \"value\": .5\n" + + " }\n" + + "}"; + + + TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler()); + Observation obs = parser.parseResource(Observation.class, input); + + assertEquals("0.5", obs.getValueQuantity().getValueElement().getValueAsString()); + } + + @Test + public void testParseInvalidNumeric_LeadingZeros() { + String input = "{\n" + + "\"resourceType\": \"Observation\",\n" + + "\"valueQuantity\": {\n" + + " \"value\": 00.5\n" + + " }\n" + + "}"; + + + TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler()); + Observation obs = parser.parseResource(Observation.class, input); + + assertEquals("0.5", obs.getValueQuantity().getValueElement().getValueAsString()); + } + + @Test + public void testParseInvalidNumeric_DoubleZeros() { + String input = "{\n" + + "\"resourceType\": \"Observation\",\n" + + "\"valueQuantity\": {\n" + + " \"value\": 00\n" + + " }\n" + + "}"; + + + TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler()); + Observation obs = parser.parseResource(Observation.class, input); + + assertEquals("0", obs.getValueQuantity().getValueElement().getValueAsString()); + } + + @Test + public void testParseInvalidNumeric2() { + String input = "{\n" + + "\"resourceType\": \"Observation\",\n" + + "\"valueQuantity\": {\n" + + " \"value\": .\n" + + " }\n" + + "}"; + + + TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler()); + try { + parser.parseResource(Observation.class, input); + } catch (DataFormatException e) { + assertThat(e.getMessage(), containsString("[element=\"value\"] Invalid attribute value \".\"")); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java index e9c6aacb86b..2cf7dea56fc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java @@ -1,9 +1,16 @@ package ca.uhn.fhir.jpa.dao.dstu2; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestDstu2Config; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; @@ -13,13 +20,13 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchCustomSearchParamTest.java index 6d93836ec5b..9622a044bff 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchCustomSearchParamTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -243,48 +243,6 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu } - @Test - public void testOverrideAndDisableBuiltInSearchParametersWithOverridingDisabled() { - myModelConfig.setDefaultSearchParamsCanBeOverridden(false); - - SearchParameter memberSp = new SearchParameter(); - memberSp.setCode("member"); - memberSp.setBase(ResourceTypeEnum.GROUP); - memberSp.setType(SearchParamTypeEnum.REFERENCE); - memberSp.setXpath("Group.member.entity"); - memberSp.setXpathUsage(XPathUsageTypeEnum.NORMAL); - memberSp.setStatus(ConformanceResourceStatusEnum.RETIRED); - mySearchParameterDao.create(memberSp, mySrd); - - SearchParameter identifierSp = new SearchParameter(); - identifierSp.setCode("identifier"); - identifierSp.setBase(ResourceTypeEnum.GROUP); - identifierSp.setType(SearchParamTypeEnum.TOKEN); - identifierSp.setXpath("Group.identifier"); - identifierSp.setXpathUsage(XPathUsageTypeEnum.NORMAL); - identifierSp.setStatus(ConformanceResourceStatusEnum.RETIRED); - mySearchParameterDao.create(identifierSp, mySrd); - - mySearchParamRegistry.forceRefresh(); - - Patient p = new Patient(); - p.addName().addGiven("G"); - IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - - Group g = new Group(); - g.addIdentifier().setSystem("urn:foo").setValue("bar"); - g.addMember().getEntity().setReference(pid); - myGroupDao.create(g); - - assertThat(myResourceLinkDao.findAll(), not(empty())); - assertThat(ListUtil.filter(myResourceIndexedSearchParamTokenDao.findAll(), new ListUtil.Filter() { - @Override - public boolean isOut(ResourceIndexedSearchParamToken object) { - return !object.getResourceType().equals("Group") || object.isMissing(); - } - }), not(empty())); - } - @Test public void testOverrideAndDisableBuiltInSearchParametersWithOverridingEnabled() { myModelConfig.setDefaultSearchParamsCanBeOverridden(true); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index a8a2d2ec5e6..0e9e981bc50 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu2; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3Test; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; @@ -426,7 +426,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { myPatientDao.create(p, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage()); + assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage()); } // Now with a forced ID diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java index 7492380a238..e082de5d4a6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java @@ -212,7 +212,7 @@ public class FhirResourceDaoDstu2UpdateTest extends BaseJpaDstu2Test { p2.setId(new IdDt("Patient/" + p1id.getIdPart())); myOrganizationDao.update(p2, mySrd); fail(); - } catch (UnprocessableEntityException e) { + } catch (InvalidRequestException e) { ourLog.error("Good", e); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2ValidateTest.java index f02a9203011..5e638fa3189 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2ValidateTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -14,7 +14,6 @@ import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; @@ -143,11 +142,15 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test { ValidationModeEnum mode = ValidationModeEnum.CREATE; String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input); - MethodOutcome outcome = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); - - String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); - ourLog.info(ooString); - assertThat(ooString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); + ourLog.info(encoded); + try { + myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + fail(); + } catch (PreconditionFailedException e) { + String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()); + ourLog.info(ooString); + assertThat(ooString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked")); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java index ff577c54a84..5c84fc6f97e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java @@ -10,13 +10,13 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.ValueSet; @@ -56,7 +56,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); } @@ -69,7 +69,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure--expiration", result.getDisplay()); } @@ -83,7 +83,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -97,7 +97,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX"); CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -111,7 +111,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = new StringDt("Systolic blood pressure at First encounter"); CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -125,7 +125,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -139,13 +139,13 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { StringDt display = null; CodingDt coding = null; CodeableConceptDt codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @Test - public void testExpandById() throws IOException { + public void testExpandById() { String resp; ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index bdc3ad3a2d7..ba4ca88a4b9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -1,9 +1,20 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestDstu3Config; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; @@ -12,7 +23,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -23,8 +34,7 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; @@ -35,7 +45,6 @@ import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -66,7 +75,7 @@ import static org.junit.Assert.fail; @ContextConfiguration(classes = {TestDstu3Config.class}) public abstract class BaseJpaDstu3Test extends BaseJpaTest { - private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; + private static IValidationSupport ourJpaValidationSupportChainDstu3; private static IFhirResourceDaoValueSet ourValueSetDao; @Autowired @@ -148,6 +157,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Qualifier("myLocationDaoDstu3") protected IFhirResourceDao myLocationDao; @Autowired + @Qualifier("myPractitionerRoleDaoDstu3") + protected IFhirResourceDao myPractitionerRoleDao; + @Autowired @Qualifier("myMediaDaoDstu3") protected IFhirResourceDao myMediaDao; @Autowired @@ -206,6 +218,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao; @Autowired + protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao; + @Autowired protected IResourceTableDao myResourceTableDao; @Autowired protected IResourceTagDao myResourceTagDao; @@ -253,7 +267,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected PlatformTransactionManager myTxManager; @Autowired - @Qualifier("myJpaValidationSupportChainDstu3") + @Qualifier("myJpaValidationSupportChain") protected IValidationSupport myValidationSupport; @Autowired @Qualifier("myValueSetDaoDstu3") @@ -263,7 +277,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; @Autowired - private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3; + private IValidationSupport myJpaValidationSupportChainDstu3; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; @Autowired @@ -356,8 +370,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @AfterClass public static void afterClassClearContextBaseJpaDstu3Test() { - ourValueSetDao.purgeCaches(); - ourJpaValidationSupportChainDstu3.flush(); + if (ourValueSetDao != null) { + ourValueSetDao.purgeCaches(); + } + if (ourJpaValidationSupportChainDstu3 != null) { + ourJpaValidationSupportChainDstu3.invalidateCaches(); + } TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java index ea21dc3fe45..6cfe5c59821 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java @@ -7,7 +7,7 @@ import org.hl7.fhir.dstu3.model.Bundle; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDocumentDstu3Test extends BaseJpaDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ConceptMapTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ConceptMapTest.java index 62311c3a6d0..5ff7d8ed05a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ConceptMapTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ConceptMapTest.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.term.TranslationMatch; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; +import ca.uhn.fhir.jpa.api.model.TranslationMatch; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java index 941900ae49d..c24c0825d8a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ContainedTest.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Reference; @@ -7,8 +11,8 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.util.TestUtil; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; public class FhirResourceDaoDstu3ContainedTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3ContainedTest.class); @@ -47,12 +51,9 @@ public class FhirResourceDaoDstu3ContainedTest extends BaseJpaDstu3Test { ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(o2)); - SearchParameterMap map; - -// map = new SearchParameterMap(); -// map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT)); -// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2))); - + SearchParameterMap map = new SearchParameterMap(); + map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(oid1, oid2))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java index 7eaa1aa5351..262dc732fa0 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java @@ -17,7 +17,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ReferentialIntegrityTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ReferentialIntegrityTest.java index 69c2d9472e6..9fd50667c61 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ReferentialIntegrityTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ReferentialIntegrityTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.util.TestUtil; @@ -24,7 +24,7 @@ public class FhirResourceDaoDstu3ReferentialIntegrityTest extends BaseJpaDstu3Te } @Test - public void testCreateUnknownReferenceFail() throws Exception { + public void testCreateUnknownReferenceFail() { Patient p = new Patient(); p.setManagingOrganization(new Reference("Organization/AAA")); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java index c9916d62c22..d30393bd467 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java @@ -1,8 +1,10 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +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.parser.IParser; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -20,6 +22,7 @@ import org.junit.Before; import org.junit.Test; import java.util.List; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -1054,6 +1057,48 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu } + @Test + public void testProgramaticallyContainedByReferenceAreStillResolvable() { + SearchParameter sp = new SearchParameter(); + sp.setUrl("http://hapifhir.io/fhir/StructureDefinition/sp-unique"); + sp.setName("MEDICATIONADMINISTRATION-INGREDIENT-MEDICATION"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setCode("medicationadministration-ingredient-medication"); + sp.addBase("MedicationAdministration"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setExpression("MedicationAdministration.medication.resolve().ingredient.item.as(Reference).resolve().code"); + mySearchParameterDao.create(sp); + mySearchParamRegistry.forceRefresh(); + + Medication ingredient = new Medication(); + ingredient.getCode().addCoding().setSystem("system").setCode("code"); + + Medication medication = new Medication(); + medication.addIngredient().setItem(new Reference(ingredient)); + + MedicationAdministration medAdmin = new MedicationAdministration(); + medAdmin.setMedication(new Reference(medication)); + + myMedicationAdministrationDao.create(medAdmin); + + runInTransaction(()->{ + List tokens = myResourceIndexedSearchParamTokenDao + .findAll() + .stream() + .filter(t -> t.getParamName().equals("medicationadministration-ingredient-medication")) + .collect(Collectors.toList()); + ourLog.info("Tokens: {}", tokens); + assertEquals(tokens.toString(), 1, tokens.size()); + + }); + + SearchParameterMap map = new SearchParameterMap(); + map.add("medicationadministration-ingredient-medication", new TokenParam("system","code")); + assertEquals(1, myMedicationAdministrationDao.search(map).size().intValue()); + + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchDistanceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchDistanceTest.java new file mode 100644 index 00000000000..5867db54e93 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchDistanceTest.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.util.CoordCalculatorTest; +import ca.uhn.fhir.rest.param.TokenParam; +import org.hl7.fhir.dstu3.model.Location; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; + +import java.util.List; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +public class FhirResourceDaoDstu3SearchDistanceTest extends BaseJpaDstu3Test { + @Autowired + MatchUrlService myMatchUrlService; + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Test + public void testNearSearchDistanceNoDistance() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LONGITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + latitude + ":" + longitude, + myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + + @Test + public void testNearSearchDistanceZero() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LONGITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + latitude + ":" + longitude + + "&" + + Location.SP_NEAR_DISTANCE + "=0||", + myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + + @Test + public void testNearSearchApproximate() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + ":" + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + bigEnoughDistance + "|http://unitsofmeasure.org|km", myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + ":" + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + tooSmallDistance + "|http://unitsofmeasure.org|km", myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids.size(), is(0)); + } + + } + + @Test + public void testBadCoordsFormat() { + assertInvalidNearFormat("1:2:3"); + assertInvalidNearFormat("1:"); + assertInvalidNearFormat(":"); + assertInvalidNearFormat(""); + } + + private void assertInvalidNearFormat(String theCoords) { + SearchParameterMap map = new SearchParameterMap(); + map.add(Location.SP_NEAR, new TokenParam(theCoords)); + map.setLoadSynchronous(true); + try { + myLocationDao.search(map); + fail(); + } catch (InvalidDataAccessApiUsageException e) { + assertEquals("Invalid position format '" + theCoords + "'. Required format is 'latitude:longitude'", e.getCause().getMessage()); + } + } + + @Test + public void testNearMissingLat() { + SearchParameterMap map = new SearchParameterMap(); + map.add(Location.SP_NEAR, new TokenParam(":2")); + map.setLoadSynchronous(true); + try { + myLocationDao.search(map); + fail(); + } catch (InvalidDataAccessApiUsageException e) { + assertEquals("Invalid position format ':2'. Both latitude and longitude must be provided.", e.getCause().getMessage()); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index 461e75c5806..7cf1475a656 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -1,12 +1,10 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.*; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; -import ca.uhn.fhir.jpa.util.CoordCalculatorTest; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; @@ -36,8 +34,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; @@ -59,9 +56,6 @@ import static org.mockito.Mockito.mock; public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SearchNoFtTest.class); - @Autowired - MatchUrlService myMatchUrlService; - @Before public void beforeDisableResultReuse() { myDaoConfig.setReuseCachedSearchResultsForMillis(null); @@ -368,16 +362,16 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { obs.addIdentifier().setSystem("urn:system").setValue("FOO"); obs.setDevice(new Reference(devId)); obs.setSubject(new Reference(pid0)); - myObservationDao.create(obs, mySrd).getId(); + obs.setCode(new CodeableConcept(new Coding("sys", "val", "disp"))); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); } - SearchParameterMap params; + SearchParameterMap params = new SearchParameterMap(); - // Not currently working -// params = new SearchParameterMap(); -// params.setLoadSynchronous(true); -// params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID")); -// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); + // Target exists and is linked + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); // No targets exist params = new SearchParameterMap(); @@ -495,7 +489,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { assertEquals(2, results.size()); List actual = toUnqualifiedVersionlessIds( - mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam((ParamPrefixEnum) null, 123, "http://foo", "UNIT")))); + mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://foo", "UNIT")))); assertThat(actual, contains(id)); } @@ -1592,7 +1586,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { dr01.setSubject(new Reference(patientId01)); IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); - ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[]{patientId01, patientId02, obsId01, obsId02, drId01}); + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", patientId01, patientId02, obsId01, obsId02, drId01); List result = toList(myObservationDao .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01")))); @@ -1697,7 +1691,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { Date after = new Date(); ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick(); - ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[]{patientId01, locId01, obsId01, obsId02}); + ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", patientId01, locId01, obsId01, obsId02); List result; SearchParameterMap params; @@ -1760,7 +1754,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { dr01.setSubject(new Reference(patientId01)); IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); - ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[]{patientId01, patientId02, obsId01, obsId02, drId01}); + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", patientId01, patientId02, obsId01, obsId02, drId01); List result = toList( myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId01")))); @@ -2851,7 +2845,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { ourLog.info("Initial size: " + value.size()); for (IBaseResource next : value.getResources(0, value.size())) { ourLog.info("Deleting: {}", next.getIdElement()); - myDeviceDao.delete((IIdType) next.getIdElement(), mySrd); + myDeviceDao.delete(next.getIdElement(), mySrd); } value = myDeviceDao.search(new SearchParameterMap()); @@ -2870,7 +2864,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { public void testSearchWithRevIncludes() { final String methodName = "testSearchWithRevIncludes"; TransactionTemplate txTemplate = new TransactionTemplate(myTransactionMgr); - txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); IIdType pid = txTemplate.execute(new TransactionCallback() { @Override @@ -2888,7 +2882,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { }); SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_RES_ID, new StringParam(pid.getIdPart())); + map.add(IAnyResource.SP_RES_ID, new StringParam(pid.getIdPart())); map.addRevInclude(Condition.INCLUDE_PATIENT); IBundleProvider results = myPatientDao.search(map); List foundResources = results.getResources(0, results.size()); @@ -3473,112 +3467,6 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { assertThat(ids.toString(), ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB")); } - @Test - public void testNearSearchDistanceNoDistance() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_CHIN; - double longitude = CoordCalculatorTest.LONGITUDE_CHIN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + latitude + ":" + longitude, - myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - - @Test - public void testNearSearchDistanceZero() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_CHIN; - double longitude = CoordCalculatorTest.LONGITUDE_CHIN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + latitude + ":" + longitude + - "&" + - Location.SP_NEAR_DISTANCE + "=0||", - myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - - @Test - public void testNearSearchApproximate() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_UHN; - double longitude = CoordCalculatorTest.LONGITUDE_UHN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - { // In the box - double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + ":" + CoordCalculatorTest.LONGITUDE_CHIN + - "&" + - Location.SP_NEAR_DISTANCE + "=" + bigEnoughDistance + "|http://unitsofmeasure.org|km", myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - { // Outside the box - double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; - - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + ":" + CoordCalculatorTest.LONGITUDE_CHIN + - "&" + - Location.SP_NEAR_DISTANCE + "=" + tooSmallDistance + "|http://unitsofmeasure.org|km", myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids.size(), is(0)); - } - - } - - @Test - public void testBadCoordsFormat() { - assertInvalidNearFormat("1:2:3"); - assertInvalidNearFormat("1:"); - assertInvalidNearFormat(":"); - assertInvalidNearFormat(""); - } - - private void assertInvalidNearFormat(String theCoords) { - SearchParameterMap map = new SearchParameterMap(); - map.add(Location.SP_NEAR, new TokenParam(theCoords)); - map.setLoadSynchronous(true); - try { - myLocationDao.search(map); - fail(); - } catch (InvalidDataAccessApiUsageException e) { - assertEquals("Invalid position format '" + theCoords + "'. Required format is 'latitude:longitude'", e.getCause().getMessage()); - } - } - - @Test - public void testNearMissingLat() { - SearchParameterMap map = new SearchParameterMap(); - map.add(Location.SP_NEAR, new TokenParam(":2")); - map.setLoadSynchronous(true); - try { - myLocationDao.search(map); - fail(); - } catch (InvalidDataAccessApiUsageException e) { - assertEquals("Invalid position format ':2'. Both latitude and longitude must be provided.", e.getCause().getMessage()); - } - } - private String toStringMultiline(List theResults) { StringBuilder b = new StringBuilder(); for (Object next : theResults) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java index 7ea4def3e55..2ea62d032d4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.Constants; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index 4ba4f35f181..4c75ead44ff 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; @@ -9,36 +9,51 @@ import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; -import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.dstu3.model.AllergyIntolerance; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; +import org.hl7.fhir.dstu3.model.AuditEvent; +import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @@ -59,7 +74,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @Before public void before() { myDaoConfig.setMaximumExpansionSize(5000); - myCachingValidationSupport.flushCaches(); + myCachingValidationSupport.invalidateCaches(); } private CodeSystem createExternalCs() { @@ -134,8 +149,13 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + + myTermDeferredStorageSvc.saveAllDeferred(); } + @Autowired + private ITermDeferredStorageSvc myTermDeferredStorageSvc; + private void createExternalCsAndLocalVs() { CodeSystem codeSystem = createExternalCs(); @@ -501,7 +521,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred(); - IContextValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM),null, mySrd); + IValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM),null, mySrd); assertEquals(true, lookupResults.isFound()); ValueSet vs = new ValueSet(); @@ -542,8 +562,8 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { try { myValueSetDao.expand(vs, null); fail(); - } catch (InvalidRequestException e) { - assertEquals("unable to find code system http://example.com/my_code_systemAA", e.getMessage()); + } catch (PreconditionFailedException e) { + assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage()); } } @@ -691,7 +711,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); - IContextValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); + IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); assertEquals(true, outcome.isFound()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index bc693f393c5..3602a7dc61d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -1,9 +1,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; @@ -18,8 +17,19 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SortOrderEnum; 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.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; @@ -33,23 +43,45 @@ import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; -import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType; import org.hl7.fhir.dstu3.model.Quantity.QuantityComparator; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @SuppressWarnings({"unchecked", "deprecation"}) public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { @@ -60,6 +92,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { public final void after() { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); + myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); } private void assertGone(IIdType theId) { @@ -700,7 +733,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { myPatientDao.create(p, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage()); + assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage()); } // Now with a forced ID @@ -1371,6 +1404,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { @Test public void testHistoryOverMultiplePages() throws Exception { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + String methodName = "testHistoryOverMultiplePages"; Patient patient = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java index 154b807ced7..8e4b7da9697 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.primitive.InstantDt; @@ -582,15 +582,15 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { myOrganizationDao.update(p2, mySrd); fail(); } catch (UnprocessableEntityException e) { - // good + assertEquals("Existing resource ID[Patient/" + p1id.getIdPartAsLong() + "] is of type[Patient] - Cannot update with [Organization]", e.getMessage()); } try { p2.setId(new IdType("Patient/" + p1id.getIdPart())); myOrganizationDao.update(p2, mySrd); fail(); - } catch (UnprocessableEntityException e) { - ourLog.error("Good", e); + } catch (InvalidRequestException e) { + assertEquals("Incorrect resource type (Patient) for this DAO, wanted: Organization", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java index 7430378c272..84a06e342ea 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValidateTest.java @@ -10,12 +10,11 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r5.utils.IResourceValidator; @@ -77,8 +76,8 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test { ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(results.getOperationOutcome())); ourLog.info("Clearing cache"); - myValidationSupport.flushCaches(); - myFhirInstanceValidator.flushCaches(); + myValidationSupport.invalidateCaches(); + myFhirInstanceValidator.invalidateCaches(); try { myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null); @@ -323,11 +322,17 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test { ValidationModeEnum mode = ValidationModeEnum.CREATE; String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input); - MethodOutcome output = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); - OperationOutcome oo = (OperationOutcome) output.getOperationOutcome(); - String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); - ourLog.info(outputString); - assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); + try { + // Expected to throw exception + MethodOutcome output = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output.getOperationOutcome())); + fail(); + } catch (PreconditionFailedException e) { + OperationOutcome oo = (OperationOutcome) e.getOperationOutcome(); + String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); + ourLog.info(outputString); + assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked")); + } } @Test @@ -471,11 +476,17 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test { p.setActive(true); String raw = myFhirCtx.newJsonParser().encodeResourceToString(p); - MethodOutcome outcome = myPatientDao.validate(p, null, raw, EncodingEnum.JSON, null, null, mySrd); + try { + MethodOutcome outcome = myPatientDao.validate(p, null, raw, EncodingEnum.JSON, null, null, mySrd); - String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); - ourLog.info("OO: {}", encoded); - assertThat(encoded, containsString("No issues detected")); + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); + ourLog.info("OO: {}", encoded); + assertThat(encoded, containsString("No issues detected")); + } catch (PreconditionFailedException e) { + // not expected, but let's log the error + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); + fail(e.toString()); + } } @AfterClass diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java index 0180911b8b2..265e08e74d9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java @@ -1,10 +1,17 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.TestUtil; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.junit.AfterClass; @@ -16,8 +23,15 @@ import org.springframework.transaction.annotation.Transactional; import java.io.IOException; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { @@ -49,7 +63,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { // good } - ValueSet vs = myValidationSupport.fetchResource(myFhirCtx, ValueSet.class, "http://hl7.org/fhir/ValueSet/endpoint-payload-type"); + ValueSet vs = myValidationSupport.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/endpoint-payload-type"); myValueSetDao.update(vs); vs = myValueSetDao.read(new IdType("ValueSet/endpoint-payload-type")); @@ -139,7 +153,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); } @@ -152,7 +166,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure--expiration", result.getDisplay()); } @@ -166,7 +180,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -180,7 +194,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -194,7 +208,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = new StringType("Systolic blood pressure at First encounter"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -208,7 +222,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -223,7 +237,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { Coding coding = null; CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -233,10 +247,10 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test { IPrimitiveType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/v2-0487"); - StringType code = new StringType("BRN"); - StringType system = new StringType("http://hl7.org/fhir/v2/0487"); - ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); + StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); + StringType code = new StringType("male"); + StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); ourLog.info(result.getMessage()); assertTrue(result.getMessage(), result.isResult()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index da04d0461e2..4218f6e8c2b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -1,34 +1,44 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.GZipUtil; import ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.model.entity.ResourceTag; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Bundle.*; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; -import org.mockito.ArgumentCaptor; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @@ -42,11 +52,23 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java index 4631eab5892..7fb585a7b4d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java @@ -3,12 +3,13 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.config.TestDstu3Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; -import ca.uhn.test.concurrency.PointcutLatch; -import ca.uhn.fhir.jpa.util.ExpungeOptions; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.test.concurrency.PointcutLatch; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; @@ -20,11 +21,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = {TestDstu3Config.class}) -public class ExpungeHookTest { +public class ExpungeHookTest extends BaseJpaDstu3Test { @Autowired private IFhirResourceDaoPatient myPatientDao; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java index d3c72dfe589..c96f5e18d18 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.jpa.config.TestDstu3Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.test.concurrency.PointcutLatch; import com.google.common.collect.Sets; import org.apache.commons.lang3.builder.ToStringBuilder; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 91a5702e629..c4fc933cc49 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -1,35 +1,53 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; 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.partition.IPartitionLookupSvc; 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.api.svc.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.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; -import ca.uhn.fhir.jpa.term.api.*; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; @@ -43,10 +61,9 @@ import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent; import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent; @@ -82,9 +99,11 @@ import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestR4Config.class}) public abstract class BaseJpaR4Test extends BaseJpaTest { - private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4; + private static IValidationSupport ourJpaValidationSupportChainR4; private static IFhirResourceDaoValueSet ourValueSetDao; + @Autowired + protected IPartitionLookupSvc myPartitionConfigSvc; @Autowired protected ITermReadSvc myHapiTerminologySvc; @Autowired @@ -140,6 +159,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myCarePlanDaoR4") protected IFhirResourceDao myCarePlanDao; @Autowired + @Qualifier("myCareTeamDaoR4") + protected IFhirResourceDao myCareTeamDao; + @Autowired @Qualifier("myCodeSystemDaoR4") protected IFhirResourceDaoCodeSystem myCodeSystemDao; @Autowired @@ -167,11 +189,16 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected DaoConfig myDaoConfig; @Autowired + protected PartitionSettings myPartitionSettings; + @Autowired protected ModelConfig myModelConfig; @Autowired @Qualifier("myDeviceDaoR4") protected IFhirResourceDao myDeviceDao; @Autowired + @Qualifier("myProvenanceDaoR4") + protected IFhirResourceDao myProvenanceDao; + @Autowired @Qualifier("myDiagnosticReportDaoR4") protected IFhirResourceDao myDiagnosticReportDao; @Autowired @@ -203,6 +230,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myLocationDaoR4") protected IFhirResourceDao myLocationDao; @Autowired + @Qualifier("myPractitionerRoleDaoR4") + protected IFhirResourceDao myPractitionerRoleDao; + @Autowired @Qualifier("myMediaDaoR4") protected IFhirResourceDao myMediaDao; @Autowired @@ -270,6 +300,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected IResourceTagDao myResourceTagDao; @Autowired + protected IResourceHistoryTagDao myResourceHistoryTagDao; + @Autowired protected ISearchCoordinatorSvc mySearchCoordinatorSvc; @Autowired(required = false) protected IFulltextSearchSvc mySearchDao; @@ -318,7 +350,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected PlatformTransactionManager myTxManager; @Autowired - @Qualifier("myJpaValidationSupportChainR4") + @Qualifier("myJpaValidationSupportChain") protected IValidationSupport myValidationSupport; @Autowired @Qualifier("myValueSetDaoR4") @@ -339,13 +371,15 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { protected SubscriptionRegistry mySubscriptionRegistry; protected IServerInterceptor myInterceptor; @Autowired - private JpaValidationSupportChainR4 myJpaValidationSupportChainR4; + private IValidationSupport myJpaValidationSupportChainR4; private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor; private List mySystemInterceptors; @Autowired private DaoRegistry myDaoRegistry; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; + @Autowired + private IdHelperService myIdHelperService; @After() public void afterCleanupDao() { @@ -374,6 +408,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); termDeferredStorageSvc.clearDeferred(); + + myIdHelperService.clearCache(); } @After() @@ -410,8 +446,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED); } - @Before - public void beforePurgeDatabase() { + @After + public void afterPurgeDatabase() { purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry, myBulkDataExportSvc); } @@ -500,7 +536,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @AfterClass public static void afterClassClearContextBaseJpaR4Test() { ourValueSetDao.purgeCaches(); - ourJpaValidationSupportChainR4.flush(); + ourJpaValidationSupportChainR4.invalidateCaches(); TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java index 550d959b96d..fbf1a3aae18 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.SortOrderEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java index ee797ed0504..9eab9c91901 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java index 52070c17146..4caf3942cf6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java @@ -7,7 +7,7 @@ import org.hl7.fhir.r4.model.Bundle; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDocumentR4Test extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java index 225bd0f61fb..f6f956fc470 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java @@ -1,11 +1,11 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; -import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; +import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.StringParam; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java index 4fb3bc073f3..00a88ecfe44 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java @@ -1,12 +1,9 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.term.TranslationMatch; -import ca.uhn.fhir.jpa.term.TranslationRequest; -import ca.uhn.fhir.jpa.term.TranslationResult; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.jpa.api.model.TranslationMatch; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.api.model.TranslationResult; 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.*; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; @@ -22,7 +19,6 @@ import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; -import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; public class FhirResourceDaoR4ConceptMapTest extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index 56bec7d4391..5403f473173 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -1,28 +1,40 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.SampledData; +import org.hl7.fhir.r4.model.SearchParameter; import org.junit.After; import org.junit.AfterClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.PageRequest; import java.io.IOException; import java.util.Date; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class); @@ -31,6 +43,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { public void afterResetDao() { myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy()); + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); } @Test @@ -62,7 +75,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { } @Test - public void testCreateWithUuidResourceStrategy() { + public void testCreateWithUuidServerResourceStrategy() { myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); Patient p = new Patient(); @@ -76,6 +89,22 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { } + @Test + public void testCreateWithUuidServerResourceStrategy_ClientIdNotAllowed() { + myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); + myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED); + + Patient p = new Patient(); + p.addName().setFamily("FAM"); + IIdType id = myPatientDao.create(p).getId().toUnqualified(); + + assertThat(id.getIdPart(), matchesPattern("[a-z0-9]{8}-.*")); + + p = myPatientDao.read(id); + assertEquals("FAM", p.getNameFirstRep().getFamily()); + + } + /** * See #1352 */ @@ -149,7 +178,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { p = new Patient(); p.setActive(false); try { - myPatientDao.create(p).getId(); + myPatientDao.create(p); fail(); } catch (ResourceVersionConflictException e) { // good @@ -280,6 +309,26 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { } + @Test + public void testOverrideBuiltInSearchParamFailsIfDisabled() { + myModelConfig.setDefaultSearchParamsCanBeOverridden(false); + + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-birthdate"); + sp.setType(Enumerations.SearchParamType.DATE); + sp.setCode("birthdate"); + sp.setExpression("Patient.birthDate"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + try { + mySearchParameterDao.update(sp); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Can not override built-in search parameter Patient:birthdate because overriding is disabled on this server", e.getMessage()); + } + + } + @AfterClass public static void afterClassClearContext() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java index 65a50f3c335..00c12994e66 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java @@ -1,12 +1,18 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; import org.junit.AfterClass; import org.junit.Test; import org.slf4j.Logger; @@ -14,11 +20,20 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4DeleteTest.class); + @After + public void after() { + myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled()); + } + @Test public void testDeleteMarksResourceAndVersionAsDeleted() { @@ -29,19 +44,19 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { myPatientDao.delete(id); // Table should be marked as deleted - runInTransaction(()->{ + runInTransaction(() -> { ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get(); assertNotNull(resourceTable.getDeleted()); assertTrue(resourceTable.isDeleted()); }); // Current version should be marked as deleted - runInTransaction(()->{ + runInTransaction(() -> { ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1); assertNull(resourceTable.getDeleted()); assertNotNull(resourceTable.getPersistentId()); }); - runInTransaction(()->{ + runInTransaction(() -> { ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2); assertNotNull(resourceTable.getDeleted()); }); @@ -66,6 +81,23 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { } + @Test + public void testDeleteDisabled() { + myDaoConfig.setDeleteEnabled(false); + + Patient p = new Patient(); + p.setActive(true); + IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + try { + myPatientDao.delete(pId); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Resource deletion is not permitted on this server", e.getMessage()); + } + } + + @Test public void testDeleteCircularReferenceInTransaction() throws IOException { @@ -157,14 +189,14 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { myPatientDao.delete(id); // Table should be marked as deleted - runInTransaction(()->{ + runInTransaction(() -> { ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get(); assertNotNull(resourceTable.getDeleted()); }); // Mark the current history version as not-deleted even though the actual resource // table entry is marked deleted - runInTransaction(()->{ + runInTransaction(() -> { ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2); resourceTable.setDeleted(null); myResourceHistoryTableDao.save(resourceTable); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java index 90f2afc95d4..e28dd70b6a5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java @@ -17,7 +17,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java index 1a8ad31efe9..ce144c0ab36 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java @@ -1,29 +1,31 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.UriParam; -import ca.uhn.fhir.rest.param.UriParamQualifierEnum; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.RiskAssessment; +import org.hl7.fhir.r4.model.ValueSet; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import java.time.Instant; -import java.util.Date; import java.util.List; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; @SuppressWarnings({"Duplicates"}) public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test { @@ -188,22 +190,21 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test { map = new SearchParameterMap(); map.setLoadSynchronous(true); - map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s", - idVal))); + map.add(Constants.PARAM_FILTER, new StringParam(String.format("status eq active or _id eq %s", idVal))); + myCaptureQueriesListener.clear(); + found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map)); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(0); + assertThat(found, Matchers.empty()); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", idVal))); found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map)); assertThat(found, Matchers.empty()); map = new SearchParameterMap(); map.setLoadSynchronous(true); - map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", - idVal))); - found = toUnqualifiedVersionlessIdValues(myEncounterDao.search(map)); - assertThat(found, Matchers.empty()); - - map = new SearchParameterMap(); - map.setLoadSynchronous(true); - map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", - idVal))); + map.add(Constants.PARAM_FILTER, new StringParam(String.format("_id eq %s", idVal))); found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); assertThat(found, containsInAnyOrder(id1)); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java index a244e6ab7eb..54f08074aba 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java @@ -2,12 +2,10 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.interceptor.executor.InterceptorService; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -31,10 +29,18 @@ import java.util.ArrayList; import java.util.List; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.in; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.nullable; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class FhirResourceDaoR4InterceptorTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4InterceptorTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java index 1f430438ab3..35c203ed6f4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java @@ -1,8 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -13,13 +12,9 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionTemplate; -import javax.persistence.Query; - -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { @@ -28,20 +23,13 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { @After public void afterResetDao() { - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); } - @Before - public void before() { - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); - } - @After public void afterUnregisterRestHookListener() { mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); } @Before diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 2b5380886de..59793377383 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -1,29 +1,20 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.api.config.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.server.IBundleProvider; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -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 org.hl7.fhir.instance.model.api.IBaseResource; +import ca.uhn.fhir.rest.param.ReferenceParam; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; -import org.junit.*; -import org.springframework.test.context.TestPropertySource; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; -import java.util.*; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.reset; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class); @@ -32,6 +23,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { public void afterResetDao() { myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); + myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled()); } @Before @@ -53,7 +45,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { Patient p = new Patient(); p.setId(id.getIdPart()); p.addIdentifier().setSystem("urn:system").setValue("2"); - myPatientDao.update(p).getResource(); + myPatientDao.update(p); }); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); @@ -164,6 +156,176 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { } + @Test + public void testReferenceToForcedId() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + + myCaptureQueriesListener.clear(); + myPatientDao.update(patient); + + /* + * Add a resource with a forced ID target link + */ + + myCaptureQueriesListener.clear(); + Observation observation = new Observation(); + observation.getSubject().setReference("Patient/P"); + myObservationDao.create(observation); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + // select: lookup forced ID + assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + // insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK + assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + + /* + * Add another + */ + + myCaptureQueriesListener.clear(); + observation = new Observation(); + observation.getSubject().setReference("Patient/P"); + myObservationDao.create(observation); + // select: lookup forced ID + assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + // insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK + assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + + } + + + @Test + public void testReferenceToForcedId_DeletesDisabled() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + myDaoConfig.setDeleteEnabled(false); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + + myCaptureQueriesListener.clear(); + myPatientDao.update(patient); + + /* + * Add a resource with a forced ID target link + */ + + myCaptureQueriesListener.clear(); + Observation observation = new Observation(); + observation.getSubject().setReference("Patient/P"); + myObservationDao.create(observation); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + // select: lookup forced ID + assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + // insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK + assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + + /* + * Add another + */ + + myCaptureQueriesListener.clear(); + observation = new Observation(); + observation.getSubject().setReference("Patient/P"); + myObservationDao.create(observation); + // select: no lookups needed because of cache + assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + // insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK + assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + + } + + + @Test + public void testSearchUsingForcedIdReference() { + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + myPatientDao.update(patient); + + Observation obs = new Observation(); + obs.getSubject().setReference("Patient/P"); + myObservationDao.update(obs); + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("subject", new ReferenceParam("Patient/P")); + + myCaptureQueriesListener.clear(); + assertEquals(1, myObservationDao.search(map).size().intValue()); + // Resolve forced ID, Perform search, load result + assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + + /* + * Again + */ + + myCaptureQueriesListener.clear(); + assertEquals(1, myObservationDao.search(map).size().intValue()); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + // Resolve forced ID, Perform search, load result (this time we reuse the cached forced-id resolution) + assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + } + + + @Test + public void testSearchUsingForcedIdReference_DeletedDisabled() { + myDaoConfig.setDeleteEnabled(false); + + Patient patient = new Patient(); + patient.setId("P"); + patient.setActive(true); + myPatientDao.update(patient); + + Observation obs = new Observation(); + obs.getSubject().setReference("Patient/P"); + myObservationDao.update(obs); + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("subject", new ReferenceParam("Patient/P")); + + myCaptureQueriesListener.clear(); + assertEquals(1, myObservationDao.search(map).size().intValue()); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + // Resolve forced ID, Perform search, load result + assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + + /* + * Again + */ + + myCaptureQueriesListener.clear(); + assertEquals(1, myObservationDao.search(map).size().intValue()); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + // (NO resolve forced ID), Perform search, load result + assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); + } + @AfterClass public static void afterClassClearContext() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java index 447d749fc8f..b6caa4e7a9f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java index 5f11e11475f..0738cd2dac1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java @@ -1,10 +1,9 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; @@ -254,6 +253,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test MessageHeader messageHeader = new MessageHeader(); messageHeader.setId("123"); + messageHeader.setDefinition("Hello"); Bundle bundle = new Bundle(); bundle.setType(Bundle.BundleType.MESSAGE); bundle.addEntry() @@ -345,34 +345,12 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test memberSp.setType(Enumerations.SearchParamType.REFERENCE); memberSp.setExpression("Group.member.entity"); memberSp.setStatus(Enumerations.PublicationStatus.RETIRED); - mySearchParameterDao.create(memberSp, mySrd); - - SearchParameter identifierSp = new SearchParameter(); - identifierSp.setCode("identifier"); - identifierSp.addBase("Group"); - identifierSp.setType(Enumerations.SearchParamType.TOKEN); - identifierSp.setExpression("Group.identifier"); - identifierSp.setStatus(Enumerations.PublicationStatus.RETIRED); - mySearchParameterDao.create(identifierSp, mySrd); - - mySearchParamRegistry.forceRefresh(); - - Patient p = new Patient(); - p.addName().addGiven("G"); - IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - - Group g = new Group(); - g.addIdentifier().setSystem("urn:foo").setValue("bar"); - g.addMember().getEntity().setReferenceElement(pid); - myGroupDao.create(g); - - assertThat(myResourceLinkDao.findAll(), not(empty())); - assertThat(ListUtil.filter(myResourceIndexedSearchParamTokenDao.findAll(), new ListUtil.Filter() { - @Override - public boolean isOut(ResourceIndexedSearchParamToken object) { - return !object.getResourceType().equals("Group") || object.isMissing(); - } - }), not(empty())); + try { + mySearchParameterDao.create(memberSp, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Can not override built-in search parameter Group:member because overriding is disabled on this server", e.getMessage()); + } } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchDistanceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchDistanceTest.java new file mode 100644 index 00000000000..3401fc3fbef --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchDistanceTest.java @@ -0,0 +1,140 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.util.CoordCalculatorTest; +import org.hl7.fhir.r4.model.Location; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +public class FhirResourceDaoR4SearchDistanceTest extends BaseJpaR4Test { + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Autowired + MatchUrlService myMatchUrlService; + + @Test + public void testNearSearchDistanceNoDistance() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LATITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + latitude + "|" + longitude, + myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + + @Test + public void testNearSearchDistanceZero() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LATITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + { + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + latitude + "|" + longitude + "|0", + myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + { + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + latitude + "|" + longitude + "|0.0", + myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + } + + @Test + public void testNearSearchApproximate() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + + CoordCalculatorTest.LONGITUDE_CHIN + "|" + + bigEnoughDistance, myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + + CoordCalculatorTest.LONGITUDE_CHIN + "|" + + tooSmallDistance, myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids.size(), is(0)); + } + + } + + @Test + public void testNearSearchApproximateNearAntiMeridian() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_TAVEUNI; + double longitude = CoordCalculatorTest.LONGITIDE_TAVEUNI; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); + + { // We match even when the box crosses the anti-meridian + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_TAVEUNI; + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_TAVEUNI + "|" + + CoordCalculatorTest.LONGITIDE_TAVEUNI + "|" + + bigEnoughDistance, myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids, contains(locId)); + } + { // We don't match outside a box that crosses the anti-meridian + double tooSmallDistance = CoordCalculatorTest.DISTANCE_TAVEUNI; + SearchParameterMap map = myMatchUrlService.translateMatchUrl( + "Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + + CoordCalculatorTest.LONGITUDE_CHIN + "|" + + tooSmallDistance, myFhirCtx.getResourceDefinition("Location")); + + List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); + assertThat(ids.size(), is(0)); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java index e68986912ce..ac268794c94 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.*; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index 4244e5ceb46..0784897bf5e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -1,13 +1,25 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; -import ca.uhn.fhir.jpa.util.CoordCalculatorTest; +import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; @@ -33,7 +45,13 @@ import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -44,13 +62,33 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @SuppressWarnings({"unchecked", "Duplicates"}) public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { @@ -73,6 +111,28 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { myDaoConfig.setReuseCachedSearchResultsForMillis(null); } + @Test + public void testCanonicalReference() { + StructureDefinition sd = new StructureDefinition(); + sd.getSnapshot().addElement().getBinding().setValueSet("http://foo"); + String id = myStructureDefinitionDao.create(sd).getId().toUnqualifiedVersionless().getValue(); + + { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(StructureDefinition.SP_VALUESET, new ReferenceParam("http://foo")); + List ids = toUnqualifiedVersionlessIdValues(myStructureDefinitionDao.search(map)); + assertThat(ids, contains(id)); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(StructureDefinition.SP_VALUESET, new ReferenceParam("http://foo2")); + List ids = toUnqualifiedVersionlessIdValues(myStructureDefinitionDao.search(map)); + assertThat(ids, empty()); + } + } + @Test public void testHasConditionAgeCompare() { Patient patient = new Patient(); @@ -295,6 +355,109 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(ids, empty()); } + @Test + public void testChainOnType() { + + Patient sub1 = new Patient(); + sub1.setActive(true); + sub1.addIdentifier().setSystem("foo").setValue("bar"); + String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue(); + + Group sub2 = new Group(); + sub2.setActive(true); + sub2.addIdentifier().setSystem("foo").setValue("bar"); + String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc1 = new Encounter(); + enc1.getSubject().setReference(sub1Id); + String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue(); + + Encounter enc2 = new Encounter(); + enc2.getSubject().setReference(sub2Id); + String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue(); + + Observation obs = new Observation(); + obs.getSubject().setReference(sub1Id); + myObservationDao.create(obs); + + // Log the link rows + runInTransaction(() -> myResourceLinkDao.findAll().forEach(t -> ourLog.info("ResLink: {}", t.toString()))); + + List ids; + SearchParameterMap map; + IBundleProvider results; + + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Patient").setChain(PARAM_TYPE)); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, contains(enc1Id)); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Group").setChain(PARAM_TYPE)); + results = myEncounterDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, contains(enc2Id)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Organization").setChain(PARAM_TYPE)); + try { + myEncounterDao.search(map); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Resource type \"Organization\" is not a valid target type for reference search parameter: Encounter:subject", e.getMessage()); + } + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "HelpImABug").setChain(PARAM_TYPE)); + try { + myEncounterDao.search(map); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Invalid/unsupported resource type: \"HelpImABug\"", e.getMessage()); + } + + } + + @Test + public void testChainOnType2() { + + CareTeam ct = new CareTeam(); + ct.addNote().setText("Care Team"); + IIdType ctId = myCareTeamDao.create(ct).getId().toUnqualifiedVersionless(); + + DiagnosticReport dr1 = new DiagnosticReport(); + dr1.getPerformerFirstRep().setReferenceElement(ctId); + IIdType drId1 = myDiagnosticReportDao.create(dr1).getId().toUnqualifiedVersionless(); + + DiagnosticReport dr2 = new DiagnosticReport(); + dr2.getResultsInterpreterFirstRep().setReferenceElement(ctId); + myDiagnosticReportDao.create(dr2).getId().toUnqualifiedVersionless(); + + // Log the link rows + runInTransaction(() -> myResourceLinkDao.findAll().forEach(t -> ourLog.info("ResLink: {}", t.toString()))); + + List ids; + SearchParameterMap map; + IBundleProvider results; + + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(DiagnosticReport.SP_PERFORMER, new ReferenceParam( "CareTeam").setChain(PARAM_TYPE)); + results = myDiagnosticReportDao.search(map); + ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids.toString(), ids, contains(drId1.getValue())); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + + } + /** * See #441 */ @@ -569,6 +732,41 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } + @Test + public void testHasLimitsByType() { + + Patient patient = new Patient(); + patient.setActive(true); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + Encounter encounter = new Encounter(); + encounter.setStatus(Encounter.EncounterStatus.ARRIVED); + IIdType encounterId = myEncounterDao.create(encounter).getId().toUnqualifiedVersionless(); + + Device device = new Device(); + device.setManufacturer("Acme"); + IIdType deviceId = myDeviceDao.create(device).getId().toUnqualifiedVersionless(); + + Provenance provenance = new Provenance(); + provenance.addTarget().setReferenceElement(patientId); + provenance.addTarget().setReferenceElement(encounterId); + provenance.addAgent().setWho(new Reference(deviceId)); + myProvenanceDao.create(provenance); + + String criteria = "_has:Provenance:target:agent=" + deviceId.getValue(); + SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Encounter.class)); + + map.setLoadSynchronous(true); + + myCaptureQueriesListener.clear(); + IBundleProvider results = myEncounterDao.search(map); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(0); + + List ids = toUnqualifiedVersionlessIdValues(results); + assertThat(ids, containsInAnyOrder(encounterId.getValue())); + + } + @Test public void testHasParameter() { IIdType pid0; @@ -794,10 +992,10 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); ourLog.info(toStringMultiline(results)); - ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00")); + ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00")); expect0.setResource(resource); expect0.calculateHashes(); - ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00")); + ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00")); expect1.setResource(resource); expect1.calculateHashes(); @@ -1021,6 +1219,65 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } + @Test + public void testSearchByIdParam_QueryIsMinimal() { + // With only an _id parameter + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_id", new StringParam("DiagnosticReport/123")); + myCaptureQueriesListener.clear(); + myDiagnosticReportDao.search(params).size(); + List selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); + assertEquals(1, selectQueries.size()); + + String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase(); + ourLog.debug("SQL Query:\n{}", sqlQuery); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in")); + assertEquals(0, StringUtils.countMatches(sqlQuery, "join")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_type='diagnosticreport'")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_deleted_at is null")); + } + // With an _id parameter and a standard search param + { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_id", new StringParam("DiagnosticReport/123")); + params.add("code", new TokenParam("foo", "bar")); + myCaptureQueriesListener.clear(); + myDiagnosticReportDao.search(params).size(); + List selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); + assertEquals(1, selectQueries.size()); + + String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase(); + ourLog.info("SQL Query:\n{}", sqlQuery); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "join")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "hash_sys_and_value")); + assertEquals(0, StringUtils.countMatches(sqlQuery, "diagnosticreport")); + assertEquals(0, StringUtils.countMatches(sqlQuery, "res_deleted_at")); + } + } + + @Test + public void testSearchByIdParamAndOtherSearchParam_QueryIsMinimal() { + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_id", new StringParam("DiagnosticReport/123")); + params.add("_id", new StringParam("DiagnosticReport/123")); + myCaptureQueriesListener.clear(); + myDiagnosticReportDao.search(params).size(); + List selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); + assertEquals(1, selectQueries.size()); + + String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase(); + ourLog.info("SQL Query:\n{}", sqlQuery); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_id in")); + assertEquals(0, StringUtils.countMatches(sqlQuery, "join")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_type='diagnosticreport'")); + assertEquals(1, StringUtils.countMatches(sqlQuery, "resourceta0_.res_deleted_at is null")); + } + @Test public void testSearchByIdParamAnd() { IIdType id1; @@ -3258,6 +3515,44 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } } + + @Test + public void testSearchReferenceUntyped() { + Patient p = new Patient(); + p.setActive(true); + p.setId("PAT"); + myPatientDao.update(p); + + AuditEvent audit = new AuditEvent(); + audit.setId("AUDIT"); + audit.addEntity().getWhat().setReference("Patient/PAT"); + myAuditEventDao.update(audit); + + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + try { + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_WARNING, interceptor); + + myCaptureQueriesListener.clear(); + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(AuditEvent.SP_ENTITY, new ReferenceParam("PAT")); + IBundleProvider outcome = myAuditEventDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(outcome), contains("AuditEvent/AUDIT")); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + + } finally { + myInterceptorRegistry.unregisterInterceptor(interceptor); + } + + ArgumentCaptor captor = ArgumentCaptor.forClass(HookParams.class); + verify(interceptor, times(1)).invoke(ArgumentMatchers.eq(Pointcut.JPA_PERFTRACE_WARNING), captor.capture()); + StorageProcessingMessage message = captor.getValue().get(StorageProcessingMessage.class); + assertEquals("This search uses an unqualified resource(a parameter in a chain without a resource type). This is less efficient than using a qualified type. If you know what you're looking for, try qualifying it using the form: 'entity:[resourceType]'", message.getMessage()); + } + + @Test public void testSearchWithDateAndReusesExistingJoin() { // Add a search parameter to Observation.issued, so that between that one @@ -3356,7 +3651,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } - @Test public void testSearchWithFetchSizeDefaultMaximum() { myDaoConfig.setFetchSizeDefaultMaximum(5); @@ -4275,120 +4569,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(toUnqualifiedVersionlessIdValues(outcome), contains(crId)); } - @Test - public void testNearSearchDistanceNoDistance() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_CHIN; - double longitude = CoordCalculatorTest.LATITUDE_CHIN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + latitude + "|" + longitude, - myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - - @Test - public void testNearSearchDistanceZero() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_CHIN; - double longitude = CoordCalculatorTest.LATITUDE_CHIN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - { - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + latitude + "|" + longitude + "|0", - myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - { - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + latitude + "|" + longitude + "|0.0", - myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - } - - @Test - public void testNearSearchApproximate() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_UHN; - double longitude = CoordCalculatorTest.LONGITUDE_UHN; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - { // In the box - double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" - + CoordCalculatorTest.LONGITUDE_CHIN + "|" + - bigEnoughDistance, myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - { // Outside the box - double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; - - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" - + CoordCalculatorTest.LONGITUDE_CHIN + "|" + - tooSmallDistance, myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids.size(), is(0)); - } - - } - - @Test - public void testNearSearchApproximateNearAntiMeridian() { - Location loc = new Location(); - double latitude = CoordCalculatorTest.LATITUDE_TAVEUNI; - double longitude = CoordCalculatorTest.LONGITIDE_TAVEUNI; - Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); - loc.setPosition(position); - String locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless().getValue(); - - { // We match even when the box crosses the anti-meridian - double bigEnoughDistance = CoordCalculatorTest.DISTANCE_TAVEUNI; - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_TAVEUNI + "|" - + CoordCalculatorTest.LONGITIDE_TAVEUNI + "|" + - bigEnoughDistance, myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids, contains(locId)); - } - { // We don't match outside a box that crosses the anti-meridian - double tooSmallDistance = CoordCalculatorTest.DISTANCE_TAVEUNI; - SearchParameterMap map = myMatchUrlService.translateMatchUrl( - "Location?" + - Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" - + CoordCalculatorTest.LONGITUDE_CHIN + "|" + - tooSmallDistance, myFhirCtx.getResourceDefinition("Location")); - - List ids = toUnqualifiedVersionlessIdValues(myLocationDao.search(map)); - assertThat(ids.size(), is(0)); - } - } - @Test public void testCircularReferencesDontBreakRevIncludes() { @@ -4427,7 +4607,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } - private String toStringMultiline(List theResults) { StringBuilder b = new StringBuilder(); for (Object next : theResults) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java index 3e0d4d144d2..0bb92635771 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java @@ -1,7 +1,8 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum; @@ -497,12 +498,10 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test { SearchParameterMap params; -// KHS JA When we switched _has from two queries to a nested subquery, we broke support for chains within _has -// We have decided for now to prefer the performance optimization of the subquery over the slower full capability -// params = new SearchParameterMap(); -// params.setLoadSynchronous(true); -// params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID")); -// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); + params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue())); // No targets exist params = new SearchParameterMap(); @@ -652,10 +651,10 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test { List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); ourLog.info(toStringMultiline(results)); - ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00")); + ResourceIndexedSearchParamNumber expect0 = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("1.00")); expect0.setResource(resource); expect0.calculateHashes(); - ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber("RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00")); + ResourceIndexedSearchParamNumber expect1 = new ResourceIndexedSearchParamNumber(new PartitionSettings(), "RiskAssessment", RiskAssessment.SP_PROBABILITY, new BigDecimal("2.00")); expect1.setResource(resource); expect1.calculateHashes(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 590e86b89a3..673bc5a5c3a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.Search; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java index c81ca66f935..5a0b0dc5e40 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java index 26fbb97b9ab..7c6bcca1295 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java @@ -1,16 +1,20 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticSearch; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -25,10 +29,16 @@ import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.ValueSet; import org.junit.AfterClass; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java index 5e5524b8c87..618272a8a1a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -1,15 +1,16 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR4WithLuceneDisabledConfig; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -26,7 +27,6 @@ import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; import org.junit.Before; @@ -116,7 +116,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { @Qualifier("myOrganizationDaoR4") private IFhirResourceDao myOrganizationDao; @Autowired - @Qualifier("myJpaValidationSupportChainR4") + @Qualifier("myJpaValidationSupportChain") private IValidationSupport myValidationSupport; @Autowired private IFhirSystemDao mySystemDao; @@ -309,10 +309,10 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { */ @Test public void testExpandValueSetContainingSystemIncludeWithNoCodes() throws IOException { - CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/dstu3/iar/CodeSystem-iar-citizenship-status.xml"); + CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/r4/iar/CodeSystem-iar-citizenship-status.xml"); myCodeSystemDao.create(cs); - ValueSet vs = loadResourceFromClasspath(ValueSet.class, "/dstu3/iar/ValueSet-iar-citizenship-status.xml"); + ValueSet vs = loadResourceFromClasspath(ValueSet.class, "/r4/iar/ValueSet-iar-citizenship-status.xml"); myValueSetDao.create(vs); ValueSet expansion = myValueSetDao.expandByIdentifier("http://ccim.on.ca/fhir/iar/ValueSet/iar-citizenship-status", null); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java index 2985a153fea..5904c00a8ed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.Constants; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4StructureDefinitionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4StructureDefinitionTest.java index acadeeb7efb..7195c3c0635 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4StructureDefinitionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4StructureDefinitionTest.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.util.TestUtil; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.validation.SnapshotGeneratingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.r4.model.StructureDefinition; import org.junit.After; import org.junit.AfterClass; @@ -11,10 +11,7 @@ import org.junit.Test; import java.io.IOException; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; @SuppressWarnings({"unchecked", "deprecation"}) public class FhirResourceDaoR4StructureDefinitionTest extends BaseJpaR4Test { @@ -32,12 +29,12 @@ public class FhirResourceDaoR4StructureDefinitionTest extends BaseJpaR4Test { // Create a validation chain that includes default validation support and a // snapshot generator - DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); - SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx, defaultSupport); + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(myFhirCtx); + SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx); ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapshotGenerator); // Generate the snapshot - StructureDefinition snapshot = chain.generateSnapshot(differential, "http://foo", null, "THE BEST PROFILE"); + StructureDefinition snapshot = (StructureDefinition) chain.generateSnapshot(chain, differential, "http://foo", null, "THE BEST PROFILE"); String url = "http://foo"; String webUrl = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 1b72bfc8f7e..f1f76a2be8f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; @@ -14,6 +14,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -54,7 +55,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { @Before public void before() { myDaoConfig.setMaximumExpansionSize(5000); - myCachingValidationSupport.flushCaches(); + myCachingValidationSupport.invalidateCaches(); } private CodeSystem createExternalCs() { @@ -164,6 +165,8 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { } myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + + myTerminologyDeferredStorageSvc.saveAllDeferred(); } private void createLocalCsAndVs() { @@ -593,8 +596,8 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { try { myValueSetDao.expand(vs, null); fail(); - } catch (InvalidRequestException e) { - assertEquals("Unable to find code system http://example.com/my_code_systemAA", e.getMessage()); + } catch (PreconditionFailedException e) { + assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage()); } } @@ -806,7 +809,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); - IContextValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); + IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); assertEquals(true, outcome.isFound()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java index d91bf9ad98c..facda6c685c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -1,9 +1,17 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.JpaResourceDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; @@ -18,8 +26,20 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SortOrderEnum; 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.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; @@ -41,22 +61,48 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueType; import org.hl7.fhir.r4.model.Quantity.QuantityComparator; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.reset; @SuppressWarnings({"unchecked", "deprecation", "Duplicates"}) @@ -910,7 +956,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { myPatientDao.create(p, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage()); + assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage()); } // Now with a forced ID @@ -3842,7 +3888,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { */ @Test public void testUploadExtensionStructureDefinition() { - StructureDefinition ext = myValidationSupport.fetchStructureDefinition(myFhirCtx, "http://hl7.org/fhir/StructureDefinition/familymemberhistory-type"); + StructureDefinition ext = (StructureDefinition) myValidationSupport.fetchStructureDefinition("http://hl7.org/fhir/StructureDefinition/familymemberhistory-type"); Validate.notNull(ext); myStructureDefinitionDao.update(ext); } @@ -3880,7 +3926,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { } private static List toStringList(List theUriType) { - ArrayList retVal = new ArrayList(); + ArrayList retVal = new ArrayList<>(); for (UriType next : theUriType) { retVal.add(next.getValue()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index 34f8f6c6d4b..eb8c7f80f19 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -22,7 +22,6 @@ import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; @@ -32,10 +31,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatchers; -import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.util.ProxyUtils; -import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @@ -48,8 +44,17 @@ import java.util.UUID; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -551,8 +556,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId)); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); assertThat(unformattedSql, stringContainsInOrder( - "SRC_PATH in ('ServiceRequest.subject.where(resolve() is Patient)')", - "SRC_PATH in ('ServiceRequest.performer')" + "SRC_PATH='ServiceRequest.subject.where(resolve() is Patient)'", + "SRC_PATH='ServiceRequest.performer'" )); assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); assertThat(unformattedSql, not(containsString(("RES_TYPE")))); @@ -695,7 +700,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals(1, myResourceReindexingSvc.forceReindexingPass()); assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); - runInTransaction(()->{ + runInTransaction(() -> { List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(uniques.toString(), 1, uniques.size()); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart()))); @@ -712,7 +717,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { myResourceReindexingSvc.forceReindexingPass(); assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); - runInTransaction(()->{ + runInTransaction(() -> { List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(uniques.toString(), 1, uniques.size()); assertThat(uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue(), either(equalTo("Observation/" + id2.getIdPart())).or(equalTo("Observation/" + id3.getIdPart()))); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index 669bd388853..ecc4961b9b7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -18,7 +18,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; import org.junit.*; -import org.springframework.test.context.TestPropertySource; import java.util.*; @@ -35,6 +34,8 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { public void afterResetDao() { myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); + myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); + myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy()); } @Before @@ -707,7 +708,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { p2.setId(new IdType("Patient/" + p1id.getIdPart())); myOrganizationDao.update(p2, mySrd); fail(); - } catch (UnprocessableEntityException e) { + } catch (InvalidRequestException e) { ourLog.error("Good", e); } @@ -926,6 +927,24 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { } + @Test + public void testUpdateWithUuidServerResourceStrategy_ClientIdNotAllowed() { + myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); + myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED); + + Patient p = new Patient(); + p.setId(UUID.randomUUID().toString()); + p.addName().setFamily("FAM"); + try { + myPatientDao.update(p); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("No resource exists on this server resource with ID.*, and client-assigned IDs are not enabled.")); + } + + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index 2351887144f..43dc7be6559 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; @@ -17,10 +19,10 @@ import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.io.IOUtils; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Observation.ObservationStatus; @@ -39,7 +41,10 @@ import java.util.Collections; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class); @@ -49,6 +54,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { private ITermReadSvc myTermReadSvc; @Autowired private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc; + @Autowired + private DaoRegistry myDaoRegistry; /** * Create a loinc valueset that expands to more results than the expander is willing to do @@ -127,7 +134,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3"); obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO"); oo = validateAndReturnOutcome(obs); - assertEquals(encode(oo), "Unknown code[FOO] in system[http://terminology.hl7.org/CodeSystem/observation-category]", oo.getIssueFirstRep().getDiagnostics()); + assertEquals(encode(oo), "Unknown code {http://terminology.hl7.org/CodeSystem/observation-category}FOO", oo.getIssueFirstRep().getDiagnostics()); } @@ -147,6 +154,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { } myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd); + myTerminologyDeferredStorageSvc.saveAllDeferred(); + // Create a valueset ValueSet vs = new ValueSet(); vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult"); @@ -154,7 +163,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { myValueSetDao.create(vs); myTermReadSvc.preExpandDeferredValueSetsToTerminologyTables(); - await().until(()->myTermReadSvc.isValueSetPreExpandedForCodeValidation(vs)); + await().until(() -> myTermReadSvc.isValueSetPreExpandedForCodeValidation(vs)); // Load the profile, which is just the Vital Signs profile modified to accept all loinc codes // and not just certain ones @@ -208,14 +217,33 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3"); obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO"); oo = validateAndReturnOutcome(obs); - assertEquals(encode(oo), "Unknown code[FOO] in system[http://terminology.hl7.org/CodeSystem/observation-category]", oo.getIssueFirstRep().getDiagnostics()); + assertEquals(encode(oo), "Unknown code {http://terminology.hl7.org/CodeSystem/observation-category}FOO", oo.getIssueFirstRep().getDiagnostics()); } + @Test + public void testValidateCodeableConceptWithNoSystem() { + AllergyIntolerance allergy = new AllergyIntolerance(); + allergy.getText().setStatus(Narrative.NarrativeStatus.GENERATED).getDiv().setValue("
        hi!
        "); + allergy.getClinicalStatus().addCoding().setSystem(null).setCode("active").setDisplay("Active"); + allergy.getVerificationStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-verification").setCode("confirmed").setDisplay("Confirmed"); + allergy.setPatient(new Reference("Patient/123")); - private OperationOutcome validateAndReturnOutcome(Observation theObs) { + allergy.addNote() + .setText("This is text") + .setAuthor(new Reference("Patient/123")); + + ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(allergy)); + + OperationOutcome oo = validateAndReturnOutcome(allergy); + assertThat(encode(oo), containsString("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/allergyintolerance-clinical")); + } + + @SuppressWarnings("unchecked") + private OperationOutcome validateAndReturnOutcome(T theObs) { + IFhirResourceDao dao = (IFhirResourceDao) myDaoRegistry.getResourceDao(theObs.getClass()); try { - MethodOutcome outcome = myObservationDao.validate(theObs, null, null, null, ValidationModeEnum.CREATE, null, mySrd); + MethodOutcome outcome = dao.validate(theObs, null, null, null, ValidationModeEnum.CREATE, null, mySrd); return (OperationOutcome) outcome.getOperationOutcome(); } catch (PreconditionFailedException e) { return (OperationOutcome) e.getOperationOutcome(); @@ -380,7 +408,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { } @Test - public void testValidateResourceContainingProfileDeclarationInvalid() throws Exception { + public void testValidateResourceContainingProfileDeclarationInvalid() { String methodName = "testValidateResourceContainingProfileDeclarationInvalid"; Observation input = new Observation(); @@ -395,12 +423,51 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { ValidationModeEnum mode = ValidationModeEnum.CREATE; String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input); - MethodOutcome output = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); - org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) output.getOperationOutcome(); - String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); - ourLog.info(outputString); - assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); + try { + myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + fail(); + } catch (PreconditionFailedException e) { + org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome(); + String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); + ourLog.info(outputString); + assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked")); + } + } + @Test + public void testValidateBundleContainingResourceContainingProfileDeclarationInvalid() { + String methodName = "testValidateResourceContainingProfileDeclarationInvalid"; + + Observation observation = new Observation(); + String profileUri = "http://example.com/StructureDefinition/" + methodName; + observation.getMeta().getProfile().add(new CanonicalType(profileUri)); + observation.addIdentifier().setSystem("http://acme").setValue("12345"); + observation.getEncounter().setReference("http://foo.com/Encounter/9"); + observation.setStatus(ObservationStatus.FINAL); + observation.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.TRANSACTION); + input.addEntry() + .setResource(observation) + .setFullUrl("http://example.com/Observation") + .getRequest() + .setUrl("http://example.com/Observation") + .setMethod(Bundle.HTTPVerb.POST); + + ValidationModeEnum mode = ValidationModeEnum.CREATE; + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input); + ourLog.info(encoded); + + try { + myBundleDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd); + fail(); + } catch (PreconditionFailedException e) { + org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome(); + String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); + ourLog.info(outputString); + assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked")); + } } @Test @@ -614,6 +681,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { upload("/r4/uscore/ValueSet-omb-race-category.json"); upload("/r4/uscore/ValueSet-us-core-usps-state.json"); + myTerminologyDeferredStorageSvc.saveAllDeferred(); + { String resource = loadResource("/r4/uscore/patient-resource-badcode.json"); IBaseResource parsedResource = myFhirCtx.newJsonParser().parseResource(resource); @@ -746,7 +815,6 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { } - private IBaseResource findResourceByIdInBundle(Bundle vss, String name) { IBaseResource retVal = null; for (BundleEntryComponent next : vss.getEntry()) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java index 6ea35a9edf4..c1ce1e67309 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -1,20 +1,35 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.*; -import org.junit.*; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { @@ -54,7 +69,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); } @@ -67,7 +82,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure--expiration", result.getDisplay()); } @@ -81,7 +96,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -95,7 +110,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertFalse(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -109,7 +124,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = new StringType("Systolic blood pressure at First encounter"); Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -124,7 +139,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { Coding coding = null; CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -141,7 +156,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { Coding coding = null; CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -165,7 +180,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } @@ -181,7 +196,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { StringType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -261,14 +276,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { IPrimitiveType display = null; Coding coding = null; CodeableConcept codeableConcept = null; - StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/yesnodontknow"); - StringType code = new StringType("Y"); - StringType system = new StringType("http://terminology.hl7.org/CodeSystem/v2-0136"); - ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); + StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); + StringType code = new StringType("male"); + StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); ourLog.info(result.getMessage()); assertTrue(result.getMessage(), result.isResult()); - assertEquals("Yes", result.getDisplay()); + assertEquals("Male", result.getDisplay()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java index a6776ab5529..7548c0c0fd4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.SearchParameter; @@ -11,7 +11,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index 666bd897ab0..5d51ec64887 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -596,7 +596,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { runInTransaction(()->{ myEntityManager - .createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashNormalizedPrefix = null") + .createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashNormalizedPrefix = 0") .executeUpdate(); }); @@ -609,6 +609,15 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); + runInTransaction(()->{ + ResourceIndexedSearchParamString param = myResourceIndexedSearchParamStringDao.findAll() + .stream() + .filter(t -> t.getParamName().equals("family")) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException()); + assertEquals(-6332913947530887803L, param.getHashNormalizedPrefix().longValue()); + }); + assertEquals(1, myPatientDao.search(searchParamMap).size().intValue()); } @@ -625,16 +634,16 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { runInTransaction(()->{ Long i = myEntityManager - .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity IS null", Long.class) + .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity = 0", Long.class) .getSingleResult(); assertEquals(0L, i.longValue()); myEntityManager - .createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashIdentity = null") + .createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashIdentity = 0") .executeUpdate(); i = myEntityManager - .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity IS null", Long.class) + .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity = 0", Long.class) .getSingleResult(); assertThat(i, greaterThan(1L)); @@ -645,7 +654,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { runInTransaction(()->{ Long i = myEntityManager - .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity IS null", Long.class) + .createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myHashIdentity = 0", Long.class) .getSingleResult(); assertEquals(0L, i.longValue()); }); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java new file mode 100644 index 00000000000..8a0a7ab0dc4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningR4Test.java @@ -0,0 +1,2140 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; +import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; +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.param.DateAndListParam; +import ca.uhn.fhir.rest.param.DateOrListParam; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.hamcrest.Matchers; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.SearchParameter; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.ServletException; +import java.time.LocalDate; +import java.time.Month; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +@SuppressWarnings("unchecked") +public class PartitioningR4Test extends BaseJpaR4SystemTest { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PartitioningR4Test.class); + + private MyInterceptor myPartitionInterceptor; + private LocalDate myPartitionDate; + private LocalDate myPartitionDate2; + private int myPartitionId; + private int myPartitionId2; + private boolean myHaveDroppedForcedIdUniqueConstraint; + @Autowired + private IPartitionLookupSvc myPartitionConfigSvc; + + @After + public void after() { + myPartitionInterceptor.assertNoRemainingIds(); + + myPartitionSettings.setIncludePartitionInSearchHashes(new PartitionSettings().isIncludePartitionInSearchHashes()); + myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled()); + myPartitionSettings.setAllowReferencesAcrossPartitions(new PartitionSettings().getAllowReferencesAcrossPartitions()); + + myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof MyInterceptor); + myInterceptor = null; + + if (myHaveDroppedForcedIdUniqueConstraint) { + runInTransaction(() -> { + myEntityManager.createNativeQuery("delete from HFJ_FORCED_ID").executeUpdate(); + myEntityManager.createNativeQuery("alter table HFJ_FORCED_ID add constraint IDX_FORCEDID_TYPE_FID unique (RESOURCE_TYPE, FORCED_ID)"); + }); + } + } + + @Override + @Before + public void before() throws ServletException { + super.before(); + + myPartitionSettings.setPartitioningEnabled(true); + myPartitionSettings.setIncludePartitionInSearchHashes(new PartitionSettings().isIncludePartitionInSearchHashes()); + + myDaoConfig.setUniqueIndexesEnabled(true); + + myModelConfig.setDefaultSearchParamsCanBeOverridden(true); + + myPartitionDate = LocalDate.of(2020, Month.JANUARY, 14); + myPartitionDate2 = LocalDate.of(2020, Month.JANUARY, 15); + myPartitionId = 1; + myPartitionId2 = 2; + + myPartitionInterceptor = new MyInterceptor(); + myInterceptorRegistry.registerInterceptor(myPartitionInterceptor); + + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(1).setName("PART-1")); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(2).setName("PART-2")); + myPartitionConfigSvc.createPartition(new PartitionEntity().setId(3).setName("PART-3")); + } + + @Test + public void testCreateSearchParameter_DefaultPartition() { + addCreateNoPartition(); + + SearchParameter sp = new SearchParameter(); + sp.addBase("Patient"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("extpatorg"); + sp.setName("extpatorg"); + sp.setExpression("Patient.extension('http://patext').value.as(Reference)"); + Long id = mySearchParameterDao.create(sp).getId().getIdPartAsLong(); + + runInTransaction(() -> { + ResourceTable resourceTable = myResourceTableDao.findById(id).orElseThrow(IllegalArgumentException::new); + assertNull(resourceTable.getPartitionId()); + }); + } + + @Test + public void testCreate_CrossPartitionReference_ByPid_Allowed() { + myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED); + + // Create patient in partition 1 + addCreatePartition(myPartitionId, myPartitionDate); + Patient patient = new Patient(); + patient.setActive(true); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition 2 + addCreatePartition(myPartitionId2, myPartitionDate2); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + runInTransaction(() -> { + List resLinks = myResourceLinkDao.findAll(); + ourLog.info("Resource links:\n{}", resLinks.toString()); + assertEquals(2, resLinks.size()); + assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); + assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid()); + }); + } + + @Test + public void testCreate_CrossPartitionReference_ByPid_NotAllowed() { + + // Create patient in partition 1 + addCreatePartition(myPartitionId, myPartitionDate); + Patient patient = new Patient(); + patient.setActive(true); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition 2 + addCreatePartition(myPartitionId2, myPartitionDate2); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + + try { + myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), startsWith("Resource Patient/" + patientId.getIdPart() + " not found, specified in path: Observation.subject")); + } + + } + + @Test + public void testCreate_CrossPartitionReference_ByForcedId_Allowed() { + myPartitionSettings.setAllowReferencesAcrossPartitions(PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED); + + // Create patient in partition 1 + addCreatePartition(myPartitionId, myPartitionDate); + Patient patient = new Patient(); + patient.setId("ONE"); + patient.setActive(true); + IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition 2 + addCreatePartition(myPartitionId2, myPartitionDate2); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + runInTransaction(() -> { + List resLinks = myResourceLinkDao.findAll(); + ourLog.info("Resource links:\n{}", resLinks.toString()); + assertEquals(2, resLinks.size()); + assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); + assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId()); + }); + } + + @Test + public void testCreate_CrossPartitionReference_ByForcedId_NotAllowed() { + + // Create patient in partition 1 + addCreatePartition(myPartitionId, myPartitionDate); + Patient patient = new Patient(); + patient.setId("ONE"); + patient.setActive(true); + IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition 2 + addCreatePartition(myPartitionId2, myPartitionDate2); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + + try { + myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), startsWith("Resource Patient/ONE not found, specified in path: Observation.subject")); + } + + } + + @Test + public void testCreate_SamePartitionReference_DefaultPartition_ByPid() { + // Create patient in partition NULL + addCreateNoPartitionId(myPartitionDate); + Patient patient = new Patient(); + patient.setActive(true); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition NULL + addCreateNoPartitionId(myPartitionDate); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + runInTransaction(() -> { + List resLinks = myResourceLinkDao.findAll(); + ourLog.info("Resource links:\n{}", resLinks.toString()); + assertEquals(2, resLinks.size()); + assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); + assertEquals(patientId.getIdPartAsLong(), resLinks.get(0).getTargetResourcePid()); + }); + } + + @Test + public void testCreate_SamePartitionReference_DefaultPartition_ByForcedId() { + // Create patient in partition NULL + addCreateNoPartitionId(myPartitionDate); + Patient patient = new Patient(); + patient.setId("ONE"); + patient.setActive(true); + IIdType patientId = myPatientDao.update(patient).getId().toUnqualifiedVersionless(); + + // Create observation in partition NULL + addCreateNoPartitionId(myPartitionDate); + Observation obs = new Observation(); + obs.getSubject().setReference(patientId.getValue()); + IIdType obsId = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + runInTransaction(() -> { + List resLinks = myResourceLinkDao.findAll(); + ourLog.info("Resource links:\n{}", resLinks.toString()); + assertEquals(2, resLinks.size()); + assertEquals(obsId.getIdPartAsLong(), resLinks.get(0).getSourceResourcePid()); + assertEquals(patientId.getIdPart(), resLinks.get(0).getTargetResourceId()); + }); + } + + @Test + public void testCreateSearchParameter_DefaultPartitionWithDate() { + addCreateNoPartitionId(myPartitionDate); + + SearchParameter sp = new SearchParameter(); + sp.addBase("Patient"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("extpatorg"); + sp.setName("extpatorg"); + sp.setExpression("Patient.extension('http://patext').value.as(Reference)"); + Long id = mySearchParameterDao.create(sp).getId().getIdPartAsLong(); + + runInTransaction(() -> { + // HFJ_RESOURCE + ResourceTable resourceTable = myResourceTableDao.findById(id).orElseThrow(IllegalArgumentException::new); + assertNull(resourceTable.getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); + }); + } + + + @Test + public void testCreateSearchParameter_NonDefaultPartition() { + addCreatePartition(myPartitionId, myPartitionDate); + + SearchParameter sp = new SearchParameter(); + sp.addBase("Patient"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("extpatorg"); + sp.setName("extpatorg"); + sp.setExpression("Patient.extension('http://patext').value.as(Reference)"); + try { + mySearchParameterDao.create(sp); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("Resource type SearchParameter can not be partitioned", e.getMessage()); + } + } + + @Test + public void testCreate_UnknownPartition() { + addCreatePartition(99, null); + + Patient p = new Patient(); + p.addIdentifier().setSystem("system").setValue("value"); + p.setBirthDate(new Date()); + try { + myPatientDao.create(p); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Unknown partition ID: 99", e.getMessage()); + } + + } + + @Test + public void testCreate_ServerId_NoPartition() { + addCreateNoPartition(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("system").setValue("value"); + p.setBirthDate(new Date()); + Long patientId = myPatientDao.create(p).getId().getIdPartAsLong(); + + runInTransaction(() -> { + ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); + assertNull(resourceTable.getPartitionId()); + }); + } + + + @Test + public void testCreate_ServerId_WithPartition() { + createUniqueCompositeSp(); + createRequestId(); + + addCreatePartition(myPartitionId, myPartitionDate); + Organization org = new Organization(); + org.setName("org"); + IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + + addCreatePartition(myPartitionId, myPartitionDate); + Patient p = new Patient(); + p.getMeta().addTag("http://system", "code", "diisplay"); + p.addName().setFamily("FAM"); + p.addIdentifier().setSystem("system").setValue("value"); + p.setBirthDate(new Date()); + p.getManagingOrganization().setReferenceElement(orgId); + Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + runInTransaction(() -> { + // HFJ_RESOURCE + ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); + assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); + + // HFJ_RES_TAG + List tags = myResourceTagDao.findAll(); + assertEquals(1, tags.size()); + assertEquals(myPartitionId, tags.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, tags.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_VER + ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L); + assertEquals(myPartitionId, version.getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, version.getPartitionId().getPartitionDate()); + + // HFJ_HISTORY_TAG + List historyTags = myResourceHistoryTagDao.findAll(); + assertEquals(1, historyTags.size()); + assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_VER_PROV + assertNotNull(version.getProvenance()); + assertEquals(myPartitionId, version.getProvenance().getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, version.getProvenance().getPartitionId().getPartitionDate()); + + // HFJ_SPIDX_STRING + List strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId); + ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * "))); + assertEquals(10, strings.size()); + assertEquals(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate()); + + // HFJ_SPIDX_DATE + List dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId); + ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * "))); + assertEquals(2, dates.size()); + assertEquals(myPartitionId, dates.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, dates.get(0).getPartitionId().getPartitionDate()); + assertEquals(myPartitionId, dates.get(1).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, dates.get(1).getPartitionId().getPartitionDate()); + + // HFJ_RES_LINK + List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId); + assertEquals(1, resourceLinks.size()); + assertEquals(myPartitionId, resourceLinks.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resourceLinks.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_PARAM_PRESENT + List presents = mySearchParamPresentDao.findAllForResource(resourceTable); + assertEquals(3, presents.size()); + assertEquals(myPartitionId, presents.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate()); + + // HFJ_IDX_CMP_STRING_UNIQ + List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId); + assertEquals(1, uniques.size()); + assertEquals(myPartitionId, uniques.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate()); + }); + + } + + @Test + public void testCreate_ServerId_DefaultPartition() { + createUniqueCompositeSp(); + createRequestId(); + + addCreateNoPartitionId(myPartitionDate); + Organization org = new Organization(); + org.setName("org"); + IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + + addCreateNoPartitionId(myPartitionDate); + Patient p = new Patient(); + p.getMeta().addTag("http://system", "code", "diisplay"); + p.addName().setFamily("FAM"); + p.addIdentifier().setSystem("system").setValue("value"); + p.setBirthDate(new Date()); + p.getManagingOrganization().setReferenceElement(orgId); + Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + runInTransaction(() -> { + // HFJ_RESOURCE + ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); + assertEquals(null, resourceTable.getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); + + // HFJ_RES_TAG + List tags = myResourceTagDao.findAll(); + assertEquals(1, tags.size()); + assertEquals(null, tags.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, tags.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_VER + ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, 1L); + assertEquals(null, version.getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, version.getPartitionId().getPartitionDate()); + + // HFJ_HISTORY_TAG + List historyTags = myResourceHistoryTagDao.findAll(); + assertEquals(1, historyTags.size()); + assertEquals(null, historyTags.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_VER_PROV + assertNotNull(version.getProvenance()); + assertEquals(null, version.getProvenance().getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, version.getProvenance().getPartitionId().getPartitionDate()); + + // HFJ_SPIDX_STRING + List strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId); + ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * "))); + assertEquals(10, strings.size()); + assertEquals(null, strings.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate()); + + // HFJ_SPIDX_DATE + List dates = myResourceIndexedSearchParamDateDao.findAllForResourceId(patientId); + ourLog.info("\n * {}", dates.stream().map(ResourceIndexedSearchParamDate::toString).collect(Collectors.joining("\n * "))); + assertEquals(2, dates.size()); + assertEquals(null, dates.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, dates.get(0).getPartitionId().getPartitionDate()); + assertEquals(null, dates.get(1).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, dates.get(1).getPartitionId().getPartitionDate()); + + // HFJ_RES_LINK + List resourceLinks = myResourceLinkDao.findAllForResourceId(patientId); + assertEquals(1, resourceLinks.size()); + assertEquals(null, resourceLinks.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, resourceLinks.get(0).getPartitionId().getPartitionDate()); + + // HFJ_RES_PARAM_PRESENT + List presents = mySearchParamPresentDao.findAllForResource(resourceTable); + assertEquals(3, presents.size()); + assertEquals(null, presents.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, presents.get(0).getPartitionId().getPartitionDate()); + + // HFJ_IDX_CMP_STRING_UNIQ + List uniques = myResourceIndexedCompositeStringUniqueDao.findAllForResourceIdForUnitTest(patientId); + assertEquals(1, uniques.size()); + assertEquals(null, uniques.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, uniques.get(0).getPartitionId().getPartitionDate()); + }); + + } + + + @Test + public void testCreate_ForcedId_WithPartition() { + addCreatePartition(myPartitionId, myPartitionDate); + Organization org = new Organization(); + org.setId("org"); + org.setName("org"); + IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless(); + + addCreatePartition(myPartitionId, myPartitionDate); + Patient p = new Patient(); + p.setId("pat"); + p.getManagingOrganization().setReferenceElement(orgId); + myPatientDao.update(p, mySrd); + + runInTransaction(() -> { + // HFJ_FORCED_ID + List forcedIds = myForcedIdDao.findAll(); + assertEquals(2, forcedIds.size()); + assertEquals(myPartitionId, forcedIds.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, forcedIds.get(0).getPartitionId().getPartitionDate()); + assertEquals(myPartitionId, forcedIds.get(1).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, forcedIds.get(1).getPartitionId().getPartitionDate()); + }); + + } + + @Test + public void testCreate_ForcedId_NoPartition() { + addCreateNoPartition(); + Organization org = new Organization(); + org.setId("org"); + org.setName("org"); + IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless(); + + addCreateNoPartition(); + Patient p = new Patient(); + p.setId("pat"); + p.getManagingOrganization().setReferenceElement(orgId); + myPatientDao.update(p, mySrd); + + runInTransaction(() -> { + // HFJ_FORCED_ID + List forcedIds = myForcedIdDao.findAll(); + assertEquals(2, forcedIds.size()); + assertEquals(null, forcedIds.get(0).getPartitionId()); + assertEquals(null, forcedIds.get(1).getPartitionId()); + }); + + } + + @Test + public void testCreate_ForcedId_DefaultPartition() { + addCreateNoPartitionId(myPartitionDate); + Organization org = new Organization(); + org.setId("org"); + org.setName("org"); + IIdType orgId = myOrganizationDao.update(org).getId().toUnqualifiedVersionless(); + + addCreateNoPartitionId(myPartitionDate); + Patient p = new Patient(); + p.setId("pat"); + p.getManagingOrganization().setReferenceElement(orgId); + myPatientDao.update(p, mySrd); + + runInTransaction(() -> { + // HFJ_FORCED_ID + List forcedIds = myForcedIdDao.findAll(); + assertEquals(2, forcedIds.size()); + assertEquals(null, forcedIds.get(0).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, forcedIds.get(0).getPartitionId().getPartitionDate()); + assertEquals(null, forcedIds.get(1).getPartitionId().getPartitionId()); + assertEquals(myPartitionDate, forcedIds.get(1).getPartitionId().getPartitionDate()); + }); + + } + + + @Test + public void testUpdateResourceWithPartition() { + createRequestId(); + + // Create a resource + addCreatePartition(myPartitionId, myPartitionDate); + addCreatePartition(myPartitionId, myPartitionDate); + Patient p = new Patient(); + p.getMeta().addTag("http://system", "code", "diisplay"); + p.setActive(true); + Long patientId = myPatientDao.create(p).getId().getIdPartAsLong(); + runInTransaction(() -> { + // HFJ_RESOURCE + ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); + assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); + }); + + // Update that resource + p = new Patient(); + p.setId("Patient/" + patientId); + p.setActive(false); + myPatientDao.update(p, mySrd); + + runInTransaction(() -> { + // HFJ_RESOURCE + ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new); + assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate()); + + // HFJ_RES_VER + int version = 2; + ResourceHistoryTable resVer = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(patientId, version); + assertEquals(myPartitionId, resVer.getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resVer.getPartitionId().getPartitionDate()); + + // HFJ_HISTORY_TAG + List historyTags = myResourceHistoryTagDao.findAll(); + assertEquals(2, historyTags.size()); + assertEquals(myPartitionId, historyTags.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, historyTags.get(0).getPartitionId().getPartitionDate()); + assertEquals(myPartitionId, historyTags.get(1).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, historyTags.get(1).getPartitionId().getPartitionDate()); + + // HFJ_RES_VER_PROV + assertNotNull(resVer.getProvenance()); + assertNotNull(resVer.getPartitionId()); + assertEquals(myPartitionId, resVer.getProvenance().getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, resVer.getProvenance().getPartitionId().getPartitionDate()); + + // HFJ_SPIDX_STRING + List strings = myResourceIndexedSearchParamStringDao.findAllForResourceId(patientId); + ourLog.info("\n * {}", strings.stream().map(ResourceIndexedSearchParamString::toString).collect(Collectors.joining("\n * "))); + assertEquals(10, strings.size()); + assertEquals(myPartitionId, strings.get(0).getPartitionId().getPartitionId().intValue()); + assertEquals(myPartitionDate, strings.get(0).getPartitionId().getPartitionDate()); + + }); + + } + + @Test + public void testRead_PidId_AllPartitions() { + IIdType patientId1 = createPatient(1, withActiveTrue()); + IIdType patientId2 = createPatient(2, withActiveTrue()); + + { + addReadPartition(null); + myCaptureQueriesListener.clear(); + IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId1, gotId1); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + + // Only the read columns should be used, no criteria use partition + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as ")); + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + { + addReadPartition(null); + IdType gotId2 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId2, gotId2); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + + // Only the read columns should be used, no criteria use partition + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as ")); + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + } + + @Test + public void testRead_PidId_SpecificPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue()); + IIdType patientId1 = createPatient(1, withActiveTrue()); + IIdType patientId2 = createPatient(2, withActiveTrue()); + + // Read in correct Partition + { + myCaptureQueriesListener.clear(); + addReadPartition(1); + IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId1, gotId1); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + + // Only the read columns should be used, no criteria use partition + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as ")); + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + + // Read in null Partition + { + addReadPartition(1); + try { + myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known")); + } + } + + // Read in wrong Partition + { + addReadPartition(1); + try { + myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known")); + } + } + } + + @Test + public void testRead_PidId_DefaultPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue()); + IIdType patientId1 = createPatient(1, withActiveTrue()); + createPatient(2, withActiveTrue()); + + // Read in correct Partition + { + myCaptureQueriesListener.clear(); + addDefaultReadPartition(); + IdType gotId1 = myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientIdNull, gotId1); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + + // Only the read columns should be used, no criteria use partition + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID as ")); + assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + + // Read in wrong Partition + { + addDefaultReadPartition(); + try { + myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/[0-9]+ is not known")); + } + } + } + + @Test + public void testRead_ForcedId_SpecificPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO")); + + // Read in correct Partition + addReadPartition(1); + IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId1, gotId1); + + // Read in null Partition + addReadPartition(1); + try { + myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/NULL is not known")); + } + + // Read in wrong Partition + addReadPartition(1); + try { + myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/TWO is not known")); + } + } + + @Test + public void testRead_ForcedId_DefaultPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO")); + + // Read in correct Partition + addDefaultReadPartition(); + IdType gotId1 = myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientIdNull, gotId1); + + // Read in null Partition + addDefaultReadPartition(); + try { + myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/ONE is not known")); + } + + // Read in wrong Partition + addDefaultReadPartition(); + try { + myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), matchesPattern("Resource Patient/TWO is not known")); + } + } + + @Test + public void testRead_ForcedId_AllPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("NULL")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withId("ONE")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withId("TWO")); + { + addReadPartition(null); + IdType gotId1 = myPatientDao.read(patientIdNull, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientIdNull, gotId1); + } + { + addReadPartition(null); + IdType gotId1 = myPatientDao.read(patientId1, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId1, gotId1); + } + { + // Read in wrong Partition + addReadPartition(null); + IdType gotId1 = myPatientDao.read(patientId2, mySrd).getIdElement().toUnqualifiedVersionless(); + assertEquals(patientId2, gotId1); + } + } + + @Test + public void testRead_ForcedId_AllPartition_WithDuplicate() { + dropForcedIdUniqueConstraint(); + IIdType patientIdNull = createPatient(null, withActiveTrue(), withId("FOO")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withId("FOO")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withId("FOO")); + assertEquals(patientIdNull, patientId1); + assertEquals(patientIdNull, patientId2); + + { + addReadPartition(null); + try { + myPatientDao.read(patientIdNull, mySrd); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Non-unique ID specified, can not process request", e.getMessage()); + } + } + } + + @Test + public void testSearch_MissingParamString_SearchAllPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + IIdType patientId2 = createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addReadPartition(null); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'")); + } + + // :missing=false + { + addReadPartition(null); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam().setMissing(false)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'")); + } + } + + + @Test + public void testSearch_MissingParamString_SearchOnePartition() { + createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "myparamsto1_.PARTITION_ID='1'")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'")); + } + + // :missing=false + { + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam().setMissing(false)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "myparamsst1_.PARTITION_ID='1'")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'")); + } + } + + @Test + public void testSearch_MissingParamString_SearchDefaultPartition() { + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_ACTIVE, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='true'")); + } + + // :missing=false + { + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam().setMissing(false)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_MISSING='false'")); + } + } + + @Test + public void testSearch_MissingParamReference_SearchAllPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + IIdType patientId2 = createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addReadPartition(null); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_GENERAL_PRACTITIONER, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")); + assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'")); + } + } + + @Test + public void testSearch_MissingParamReference_SearchOnePartition_IncludePartitionInHashes() { + myPartitionSettings.setIncludePartitionInSearchHashes(true); + + createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_GENERAL_PRACTITIONER, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "mysearchpa1_.PARTITION_ID='1'")); + assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")); + assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='-3438137196820602023'")); + } + } + + @Test + public void testSearch_MissingParamReference_SearchOnePartition_DontIncludePartitionInHashes() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_GENERAL_PRACTITIONER, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "mysearchpa1_.PARTITION_ID='1'")); + assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")); + assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'")); + } + } + + @Test + public void testSearch_MissingParamReference_SearchDefaultPartition() { + IIdType patientIdDefault = createPatient(null, withFamily("FAMILY")); + createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + // :missing=true + { + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_GENERAL_PRACTITIONER, new StringParam().setMissing(true)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdDefault)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "mysearchpa1_.PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "HFJ_RES_PARAM_PRESENT")); + assertEquals(1, StringUtils.countMatches(searchSql, "HASH_PRESENCE='1919227773735728687'")); + } + } + + + @Test + public void testSearch_NoParams_SearchAllPartitions() { + IIdType patientIdNull = createPatient(null, withActiveTrue()); + IIdType patientId1 = createPatient(1, withActiveTrue()); + IIdType patientId2 = createPatient(2, withActiveTrue()); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + + @Test + public void testSearch_NoParams_SearchOnePartition() { + createPatient(null, withActiveTrue()); + IIdType patientId1 = createPatient(1, withActiveTrue()); + createPatient(2, withActiveTrue()); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + } + + @Test + public void testSearch_DateParam_SearchAllPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withBirthdate("2020-04-20")); + IIdType patientId1 = createPatient(1, withBirthdate("2020-04-20")); + IIdType patientId2 = createPatient(2, withBirthdate("2020-04-20")); + createPatient(null, withBirthdate("2021-04-20")); + createPatient(1, withBirthdate("2021-04-20")); + createPatient(2, withBirthdate("2021-04-20")); + + // Date param + + addReadPartition(null); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-04-20")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date OR param + + addReadPartition(null); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateOrListParam().addOr(new DateParam("2020-04-20")).addOr(new DateParam("2020-04-22"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date AND param + + addReadPartition(null); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateAndListParam().addAnd(new DateOrListParam().addOr(new DateParam("2020"))).addAnd(new DateOrListParam().addOr(new DateParam("2020-04-20")))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // DateRangeParam + + addReadPartition(null); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateRangeParam(new DateParam("2020-01-01"), new DateParam("2020-04-25"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + } + + + @Test + public void testSearch_DateParam_SearchSpecificPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withBirthdate("2020-04-20")); + IIdType patientId1 = createPatient(1, withBirthdate("2020-04-20")); + IIdType patientId2 = createPatient(2, withBirthdate("2020-04-20")); + createPatient(null, withBirthdate("2021-04-20")); + createPatient(1, withBirthdate("2021-04-20")); + createPatient(2, withBirthdate("2021-04-20")); + + // Date param + + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-04-20")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date OR param + + addReadPartition(1); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateOrListParam().addOr(new DateParam("2020-04-20")).addOr(new DateParam("2020-04-22"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date AND param + + addReadPartition(1); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateAndListParam().addAnd(new DateOrListParam().addOr(new DateParam("2020"))).addAnd(new DateOrListParam().addOr(new DateParam("2020-04-20")))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // DateRangeParam + + addReadPartition(1); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateRangeParam(new DateParam("2020-01-01"), new DateParam("2020-04-25"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + } + + + @Test + public void testSearch_DateParam_SearchDefaultPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withBirthdate("2020-04-20")); + IIdType patientId1 = createPatient(1, withBirthdate("2020-04-20")); + IIdType patientId2 = createPatient(2, withBirthdate("2020-04-20")); + createPatient(null, withBirthdate("2021-04-20")); + createPatient(1, withBirthdate("2021-04-20")); + createPatient(2, withBirthdate("2021-04-20")); + + // Date param + + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-04-20")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date OR param + + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateOrListParam().addOr(new DateParam("2020-04-20")).addOr(new DateParam("2020-04-22"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // Date AND param + + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateAndListParam().addAnd(new DateOrListParam().addOr(new DateParam("2020"))).addAnd(new DateOrListParam().addOr(new DateParam("2020-04-20")))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + // DateRangeParam + + addDefaultReadPartition(); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateRangeParam(new DateParam("2020-01-01"), new DateParam("2020-04-25"))); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW")); + + } + + + @Test + public void testSearch_StringParam_SearchAllPartitions() { + myPartitionSettings.setIncludePartitionInSearchHashes(false); + + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + IIdType patientId2 = createPatient(2, withFamily("FAMILY")); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED")); + } + + @Test + public void testSearch_StringParam_SearchDefaultPartition() { + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + addDefaultReadPartition(); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + searchSql = searchSql.toUpperCase(); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED")); + } + + @Test + public void testSearch_StringParam_SearchOnePartition() { + createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED")); + } + + @Test + public void testSearch_StringParam_SearchAllPartitions_IncludePartitionInHashes() { + myPartitionSettings.setIncludePartitionInSearchHashes(true); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + try { + myPatientDao.search(map); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("This server is not configured to support search against all partitions", e.getMessage()); + } + } + + @Test + public void testSearch_StringParam_SearchDefaultPartition_IncludePartitionInHashes() { + myPartitionSettings.setIncludePartitionInSearchHashes(true); + + IIdType patientIdNull = createPatient(null, withFamily("FAMILY")); + createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + addDefaultReadPartition(); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + searchSql = searchSql.toUpperCase(); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED")); + } + + @Test + public void testSearch_StringParam_SearchOnePartition_IncludePartitionInHashes() { + myPartitionSettings.setIncludePartitionInSearchHashes(true); + + createPatient(null, withFamily("FAMILY")); + IIdType patientId1 = createPatient(1, withFamily("FAMILY")); + createPatient(2, withFamily("FAMILY")); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_FAMILY, new StringParam("FAMILY")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED")); + } + + @Test + public void testSearch_TagNotParam_SearchAllPartitions() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withTag("http://system", "code")); + createPatient(null, withActiveTrue(), withTag("http://system", "code2")); + createPatient(1, withActiveTrue(), withTag("http://system", "code2")); + createPatient(2, withActiveTrue(), withTag("http://system", "code2")); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_TagNotParam_SearchDefaultPartition() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code")); + createPatient(1, withActiveTrue(), withTag("http://system", "code")); + createPatient(2, withActiveTrue(), withTag("http://system", "code")); + + addDefaultReadPartition(); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + + assertThat(ids.toString(), ids, Matchers.contains(patientIdNull)); + } + + @Test + public void testSearch_TagNotParam_SearchOnePartition() { + createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + createPatient(2, withActiveTrue(), withTag("http://system", "code")); + createPatient(null, withActiveTrue(), withTag("http://system", "code2")); + createPatient(1, withActiveTrue(), withTag("http://system", "code2")); + createPatient(2, withActiveTrue(), withTag("http://system", "code2")); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_TagParam_SearchAllPartitions() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withTag("http://system", "code")); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_TagParam_SearchOnePartition() { + createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + createPatient(2, withActiveTrue(), withTag("http://system", "code")); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_TagParamNot_SearchAllPartitions() { + IIdType patientIdNull = createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId2 = createPatient(2, withActiveTrue(), withTag("http://system", "code")); + createPatient(null, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + createPatient(1, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + createPatient(2, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientIdNull, patientId1, patientId2)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_TagParamNot_SearchOnePartition() { + createPatient(null, withActiveTrue(), withTag("http://system", "code")); + IIdType patientId1 = createPatient(1, withActiveTrue(), withTag("http://system", "code")); + createPatient(2, withActiveTrue(), withTag("http://system", "code")); + createPatient(null, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + createPatient(1, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + createPatient(2, withActiveTrue(), withTag("http://system", "code"), withTag("http://system", "code2")); + + addReadPartition(1); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(patientId1)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); + } + + @Test + public void testSearch_UniqueParam_SearchAllPartitions() { + createUniqueCompositeSp(); + + IIdType id = createPatient(1, withBirthdate("2020-01-01")); + + addReadPartition(null); + + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(id)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING='Patient?birthdate=2020-01-01'")); + } + + + @Test + public void testSearch_UniqueParam_SearchOnePartition() { + createUniqueCompositeSp(); + + IIdType id = createPatient(1, withBirthdate("2020-01-01")); + + addReadPartition(1); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.setLoadSynchronous(true); + IBundleProvider results = myPatientDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(id)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + assertEquals(1, StringUtils.countMatches(searchSql, "IDX_STRING='Patient?birthdate=2020-01-01'")); + + // Same query, different partition + addReadPartition(2); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.setLoadSynchronous(true); + results = myPatientDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.empty()); + + } + + @Test + public void testSearch_RefParam_TargetPid_SearchOnePartition() { + createUniqueCompositeSp(); + + IIdType patientId = createPatient(myPartitionId, withBirthdate("2020-01-01")); + IIdType observationId = createObservation(myPartitionId, withSubject(patientId)); + + addReadPartition(myPartitionId); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + IBundleProvider results = myObservationDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(observationId)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID='1'")); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'")); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + + // Same query, different partition + addReadPartition(2); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + results = myObservationDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.empty()); + + } + + @Test + public void testSearch_RefParam_TargetPid_SearchDefaultPartition() { + createUniqueCompositeSp(); + + IIdType patientId = createPatient(null, withBirthdate("2020-01-01")); + IIdType observationId = createObservation(null, withSubject(patientId)); + + addDefaultReadPartition(); + ; + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + IBundleProvider results = myObservationDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(observationId)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'")); + assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + + // Same query, different partition + addReadPartition(2); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + results = myObservationDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.empty()); + + } + + @Test + public void testSearch_RefParam_TargetForcedId_SearchOnePartition() { + createUniqueCompositeSp(); + + IIdType patientId = createPatient(myPartitionId, withId("ONE"), withBirthdate("2020-01-01")); + IIdType observationId = createObservation(myPartitionId, withSubject(patientId)); + + addReadPartition(myPartitionId); + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + IBundleProvider results = myObservationDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(observationId)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID='1' ")); + assertEquals(1, StringUtils.countMatches(searchSql, "and forcedid0_.RESOURCE_TYPE='Patient'")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + + // Same query, different partition + addReadPartition(2); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + results = myObservationDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.empty()); + + } + + @Test + public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() { + createUniqueCompositeSp(); + + IIdType patientId = createPatient(null, withId("ONE"), withBirthdate("2020-01-01")); + IIdType observationId = createObservation(null, withSubject(patientId)); + + addDefaultReadPartition(); + ; + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + IBundleProvider results = myObservationDao.search(map); + List ids = toUnqualifiedVersionlessIds(results); + assertThat(ids, Matchers.contains(observationId)); + + String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); + ourLog.info("Search SQL:\n{}", searchSql); + assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.PARTITION_ID is null")); + assertEquals(1, StringUtils.countMatches(searchSql, "forcedid0_.RESOURCE_TYPE='Patient' ")); + assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); + + // Same query, different partition + addReadPartition(2); + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.add(Observation.SP_SUBJECT, new ReferenceParam(patientId)); + map.setLoadSynchronous(true); + results = myObservationDao.search(map); + ids = toUnqualifiedVersionlessIds(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.empty()); + + } + + @Test + public void testHistory_Instance_CorrectTenant() { + IIdType id = createPatient(1, withBirthdate("2020-01-01")); + + // Update the patient + addCreatePartition(myPartitionId, myPartitionDate); + Patient p = new Patient(); + p.setActive(false); + p.setId(id); + myPatientDao.update(p); + + addReadPartition(1); + myCaptureQueriesListener.clear(); + IBundleProvider results = myPatientDao.history(id, null, null, mySrd); + List ids = toUnqualifiedIdValues(results); + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertThat(ids, Matchers.contains(id.withVersion("2").getValue(), id.withVersion("1").getValue())); + + } + + @Test + public void testHistory_Instance_WrongTenant() { + IIdType id = createPatient(1, withBirthdate("2020-01-01")); + + // Update the patient + addCreatePartition(myPartitionId, myPartitionDate); + Patient p = new Patient(); + p.setActive(false); + p.setId(id); + myPatientDao.update(p); + + addReadPartition(2); + try { + myPatientDao.history(id, null, null, mySrd); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + } + + @Test + public void testHistory_Server() { + try { + mySystemDao.history(null, null, mySrd); + fail(); + } catch (MethodNotAllowedException e) { + assertEquals("Type- and Server- level history operation not supported on partitioned server", e.getMessage()); + } + } + + @Test + public void testHistory_Type() { + try { + myPatientDao.history(null, null, mySrd); + fail(); + } catch (MethodNotAllowedException e) { + assertEquals("Type- and Server- level history operation not supported on partitioned server", e.getMessage()); + } + } + + private void createUniqueCompositeSp() { + addCreateNoPartition(); + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-birthdate"); + sp.setType(Enumerations.SearchParamType.DATE); + sp.setCode("birthdate"); + sp.setExpression("Patient.birthDate"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp); + + addCreateNoPartition(); + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-birthdate-unique"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-birthdate"); + sp.addExtension() + .setUrl(SearchParamConstants.EXT_SP_UNIQUE) + .setValue(new BooleanType(true)); + mySearchParameterDao.update(sp); + + mySearchParamRegistry.forceRefresh(); + } + + + private void dropForcedIdUniqueConstraint() { + runInTransaction(() -> { + myEntityManager.createNativeQuery("alter table " + ForcedId.HFJ_FORCED_ID + " drop constraint " + ForcedId.IDX_FORCEDID_TYPE_FID).executeUpdate(); + }); + myHaveDroppedForcedIdUniqueConstraint = true; + } + + private void addCreatePartition(Integer thePartitionId, LocalDate thePartitionDate) { + Validate.notNull(thePartitionId); + RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(thePartitionId, thePartitionDate); + myPartitionInterceptor.addCreatePartition(requestPartitionId); + } + + private void addCreateNoPartition() { + myPartitionInterceptor.addCreatePartition(null); + } + + private void addCreateNoPartitionId(LocalDate thePartitionDate) { + RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(null, thePartitionDate); + myPartitionInterceptor.addCreatePartition(requestPartitionId); + } + + private void addReadPartition(Integer thePartitionId) { + RequestPartitionId requestPartitionId = null; + if (thePartitionId != null) { + requestPartitionId = RequestPartitionId.fromPartitionId(thePartitionId, null); + } + myPartitionInterceptor.addReadPartition(requestPartitionId); + } + + private void addDefaultReadPartition() { + RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(null, null); + myPartitionInterceptor.addReadPartition(requestPartitionId); + } + + public IIdType createPatient(Integer thePartitionId, Consumer... theModifiers) { + if (thePartitionId != null) { + addCreatePartition(thePartitionId, null); + } else { + addCreateNoPartition(); + } + + + Patient p = new Patient(); + for (Consumer next : theModifiers) { + next.accept(p); + } + + if (isNotBlank(p.getId())) { + return myPatientDao.update(p, mySrd).getId().toUnqualifiedVersionless(); + } else { + return myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + } + } + + public void createRequestId() { + when(mySrd.getRequestId()).thenReturn("REQUEST_ID"); + } + + private Consumer withActiveTrue() { + return t -> t.setActive(true); + } + + private Consumer withFamily(String theFamily) { + return t -> t.addName().setFamily(theFamily); + } + + private Consumer withBirthdate(String theBirthdate) { + return t -> t.getBirthDateElement().setValueAsString(theBirthdate); + } + + private Consumer withId(String theId) { + return t -> { + assertThat(theId, matchesPattern("[a-zA-Z0-9]+")); + t.setId(theId); + }; + } + + private Consumer withTag(String theSystem, String theCode) { + return t -> t.getMeta().addTag(theSystem, theCode, theCode); + } + + public IIdType createObservation(Integer thePartitionId, Consumer... theModifiers) { + if (thePartitionId != null) { + addCreatePartition(thePartitionId, null); + } else { + addCreateNoPartition(); + } + + + Observation observation = new Observation(); + for (Consumer next : theModifiers) { + next.accept(observation); + } + + if (isNotBlank(observation.getId())) { + return myObservationDao.update(observation, mySrd).getId().toUnqualifiedVersionless(); + } else { + return myObservationDao.create(observation, mySrd).getId().toUnqualifiedVersionless(); + } + } + + private Consumer withSubject(IIdType theSubject) { + return t -> t.getSubject().setReferenceElement(theSubject.toUnqualifiedVersionless()); + } + + @Interceptor + public static class MyInterceptor { + + + private final List myCreateRequestPartitionIds = new ArrayList<>(); + private final List myReadRequestPartitionIds = new ArrayList<>(); + + public void addCreatePartition(RequestPartitionId theRequestPartitionId) { + myCreateRequestPartitionIds.add(theRequestPartitionId); + } + + public void addReadPartition(RequestPartitionId theRequestPartitionId) { + myReadRequestPartitionIds.add(theRequestPartitionId); + } + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) + public RequestPartitionId PartitionIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) { + assertNotNull(theResource); + RequestPartitionId retVal = myCreateRequestPartitionIds.remove(0); + ourLog.info("Returning partition for create: {}", retVal); + return retVal; + } + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) + public RequestPartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) { + RequestPartitionId retVal = myReadRequestPartitionIds.remove(0); + ourLog.info("Returning partition for read: {}", retVal); + return retVal; + } + + public void assertNoRemainingIds() { + assertEquals(0, myCreateRequestPartitionIds.size()); + assertEquals(0, myReadRequestPartitionIds.size()); + } + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java new file mode 100644 index 00000000000..b8ce8d63a72 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchCoordinatorSvcImplTest.java @@ -0,0 +1,118 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.entity.SearchResult; +import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl; +import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.lang3.time.DateUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Date; +import java.util.UUID; + +import static ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl.DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test { + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + @Autowired + private ISearchDao mySearchDao; + + @Autowired + private ISearchResultDao mySearchResultDao; + + @Autowired + private ISearchCoordinatorSvc mySearchCoordinator; + + @Autowired + private ISearchCacheSvc myDataaseCacheSvc; + + @After + public void after() { + DatabaseSearchCacheSvcImpl.setMaximumResultsToDeleteInOnePassForUnitTest(DatabaseSearchCacheSvcImpl.DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS); + DatabaseSearchCacheSvcImpl.setMaximumSearchesToCheckForDeletionCandidacyForUnitTest(DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND); + } + + @Test + public void testDeleteDontMarkPreviouslyMarkedSearchesAsDeleted() { + DatabaseSearchCacheSvcImpl.setMaximumResultsToDeleteInOnePassForUnitTest(5); + DatabaseSearchCacheSvcImpl.setMaximumSearchesToCheckForDeletionCandidacyForUnitTest(10); + + runInTransaction(()->{ + assertEquals(0, mySearchDao.count()); + assertEquals(0, mySearchResultDao.count()); + }); + + // Create lots of searches + runInTransaction(()->{ + for (int i = 0; i < 20; i++) { + Search search = new Search(); + search.setCreated(DateUtils.addDays(new Date(), -1)); + search.setLastUpdated(DateUtils.addDays(new Date(), -1), DateUtils.addDays(new Date(), -1)); + search.setUuid(UUID.randomUUID().toString()); + search.setSearchType(SearchTypeEnum.SEARCH); + search.setStatus(SearchStatusEnum.FINISHED); + mySearchDao.save(search); + + // Add a bunch of search results to a few (enough that it will take multiple passes) + if (i < 3) { + for (int j = 0; j < 10; j++) { + SearchResult sr = new SearchResult(search); + sr.setOrder(j); + sr.setResourcePid((long) j); + mySearchResultDao.save(sr); + } + } + + } + }); + + runInTransaction(()->{ + assertEquals(20, mySearchDao.count()); + assertEquals(30, mySearchResultDao.count()); + }); + + myDataaseCacheSvc.pollForStaleSearchesAndDeleteThem(); + runInTransaction(()->{ + // We should delete up to 10, but 3 don't get deleted since they have too many results to delete in one pass + assertEquals(13, mySearchDao.count()); + assertEquals(3, mySearchDao.countDeleted()); + // We delete a max of 5 results per search, so half are gone + assertEquals(15, mySearchResultDao.count()); + }); + + myDataaseCacheSvc.pollForStaleSearchesAndDeleteThem(); + runInTransaction(()->{ + // Once again we attempt to delete 10, but the first 3 don't get deleted and still remain + // (total is 6 because 3 weren't deleted, and they blocked another 3 that might have been) + assertEquals(6, mySearchDao.count()); + assertEquals(6, mySearchDao.countDeleted()); + assertEquals(0, mySearchResultDao.count()); + }); + + myDataaseCacheSvc.pollForStaleSearchesAndDeleteThem(); + runInTransaction(()->{ + assertEquals(0, mySearchDao.count()); + assertEquals(0, mySearchDao.countDeleted()); + assertEquals(0, mySearchResultDao.count()); + }); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java index 2fb74964ecf..87d30dc8172 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -3,6 +3,8 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; @@ -15,8 +17,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Sets; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Consent; @@ -65,6 +66,7 @@ public class SearchParamExtractorR4Test { obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE"); SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); Set tokens = extractor.extractSearchParamTokens(obs); assertEquals(1, tokens.size()); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); @@ -79,6 +81,7 @@ public class SearchParamExtractorR4Test { sp.addUseContext().setCode(new Coding().setSystem("http://system").setCode("code")); SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); Set tokens = extractor.extractSearchParamTokens(sp); assertEquals(1, tokens.size()); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); @@ -108,6 +111,7 @@ public class SearchParamExtractorR4Test { consent.setSource(new Reference().setReference("Consent/999")); SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE); assertNotNull(param); ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(consent); @@ -117,6 +121,24 @@ public class SearchParamExtractorR4Test { } + @Test + public void testExtractSearchParamTokenTest() { + Patient p = new Patient(); + p.addIdentifier().setSystem("sys").setValue("val"); + + SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); + RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER); + assertNotNull(param); + ISearchParamExtractor.SearchParamSet params = extractor.extractSearchParamTokens(p, param); + assertEquals(1, params.size()); + ResourceIndexedSearchParamToken paramValue = (ResourceIndexedSearchParamToken) params.iterator().next(); + assertEquals("identifier", paramValue.getParamName()); + assertEquals("sys", paramValue.getSystem()); + assertEquals("val", paramValue.getValue()); + } + + @Test public void testExtensionContainingReference() { String path = "Patient.extension('http://patext').value.as(Reference)"; @@ -227,7 +249,7 @@ public class SearchParamExtractorR4Test { @BeforeClass public static void beforeClass() { - ourValidationSupport = new DefaultProfileValidationSupport(); + ourValidationSupport = new DefaultProfileValidationSupport(ourCtx); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index 1af9a906c98..b141527cd6d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -1,12 +1,24 @@ package ca.uhn.fhir.jpa.dao.r5; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR5Config; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor; @@ -15,20 +27,19 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.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.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; @@ -42,9 +53,8 @@ import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; @@ -76,7 +86,7 @@ import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestR5Config.class}) public abstract class BaseJpaR5Test extends BaseJpaTest { - private static JpaValidationSupportChainR5 ourJpaValidationSupportChainR5; + private static IValidationSupport ourJpaValidationSupportChainR5; private static IFhirResourceDaoValueSet ourValueSetDao; @Autowired @@ -296,7 +306,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @Autowired protected PlatformTransactionManager myTxManager; @Autowired - @Qualifier("myJpaValidationSupportChainR5") + @Qualifier("myJpaValidationSupportChain") protected IValidationSupport myValidationSupport; @Autowired @Qualifier("myValueSetDaoR5") @@ -317,7 +327,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { protected SubscriptionRegistry mySubscriptionRegistry; protected IServerInterceptor myInterceptor; @Autowired - private JpaValidationSupportChainR5 myJpaValidationSupportChainR5; + private IValidationSupport myJpaValidationSupportChain; private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor; private List mySystemInterceptors; @Autowired @@ -359,7 +369,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @After() public void afterGrabCaches() { ourValueSetDao = myValueSetDao; - ourJpaValidationSupportChainR5 = myJpaValidationSupportChainR5; + ourJpaValidationSupportChainR5 = myJpaValidationSupportChain; } @Before @@ -440,7 +450,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @AfterClass public static void afterClassClearContextBaseJpaR5Test() { ourValueSetDao.purgeCaches(); - ourJpaValidationSupportChainR5.flush(); + ourJpaValidationSupportChainR5.invalidateCaches(); TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java index 5eb009a2e55..33176439681 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java @@ -4,7 +4,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java index 169e58564e9..9175d898e40 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java @@ -2,8 +2,9 @@ package ca.uhn.fhir.jpa.delete; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.util.DeleteConflict; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java index a95edb5ed59..085f98c340b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java @@ -2,8 +2,10 @@ package ca.uhn.fhir.jpa.delete; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import org.junit.Test; @@ -49,6 +51,8 @@ public class DeleteConflictServiceTest { DeleteConflictService myDeleteConflictService() { return new DeleteConflictService(); } @Bean DaoConfig myDaoConfig() { return new DaoConfig(); } + @Bean + PartitionSettings partitionSettings() { return new PartitionSettings(); } } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/graphql/JpaStorageServicesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/graphql/JpaStorageServicesTest.java new file mode 100644 index 00000000000..dc3a0cd2667 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/graphql/JpaStorageServicesTest.java @@ -0,0 +1,87 @@ +package ca.uhn.fhir.jpa.graphql; + +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.TestR4Config; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Appointment; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.utilities.graphql.Argument; +import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; +import org.hl7.fhir.utilities.graphql.StringValue; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +@ContextConfiguration(classes = {TestR4Config.class}) +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class JpaStorageServicesTest extends BaseJpaR4Test { + + @After + public void after() { + myDaoConfig.setFilterParameterEnabled(new DaoConfig().isFilterParameterEnabled()); + } + + @Before + public void before() { + myDaoConfig.setFilterParameterEnabled(true); + } + + @Autowired + private IGraphQLStorageServices mySvc; + + private String createSomeAppointment() { + CodeableConcept someCodeableConcept = new CodeableConcept(new Coding("TEST_SYSTEM", "TEST_CODE", "TEST_DISPLAY")); + Appointment someAppointment = new Appointment(); + someAppointment.setAppointmentType(someCodeableConcept); + return myAppointmentDao.create(someAppointment).getId().getIdPart(); + } + + @Test + public void testListResourcesGraphqlArgumentConversion() { + String appointmentId = createSomeAppointment(); + + Argument argument = new Argument("appointment_type", new StringValue("TEST_CODE")); + + List result = new ArrayList<>(); + mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result); + + Assert.assertFalse(result.isEmpty()); + Assert.assertTrue(result.stream().anyMatch((it) -> it.getIdElement().getIdPart().equals(appointmentId))); + } + + @Test + public void testListResourceGraphqlFilterArgument() { + String appointmentId = createSomeAppointment(); + + Argument argument = new Argument("_filter", new StringValue("appointment-type eq TEST_CODE")); + + List result = new ArrayList<>(); + mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result); + + Assert.assertFalse(result.isEmpty()); + Assert.assertTrue(result.stream().anyMatch((it) -> it.getIdElement().getIdPart().equals(appointmentId))); + } + + @Test(expected = InvalidRequestException.class) + public void testListResourceGraphqlInvalidException() { + Argument argument = new Argument("test", new StringValue("some test value")); + + List result = new ArrayList<>(); + mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java new file mode 100644 index 00000000000..8a323060656 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java @@ -0,0 +1,158 @@ +package ca.uhn.fhir.jpa.partition; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.model.util.ProviderConstants; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = PartitionManagementProviderTest.MyConfig.class) +public class PartitionManagementProviderTest { + + private static final Logger ourLog = LoggerFactory.getLogger(PartitionManagementProviderTest.class); + private static FhirContext ourCtx = FhirContext.forR4(); + @ClassRule + public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx); + @MockBean + private IPartitionLookupSvc myPartitionConfigSvc; + @Autowired + private PartitionManagementProvider myPartitionManagementProvider; + private IGenericClient myClient; + + @Before + public void before() { + ourServerRule.getRestfulServer().registerProvider(myPartitionManagementProvider); + myClient = ourServerRule.getFhirClient(); + myClient.registerInterceptor(new LoggingInterceptor(false)); + } + + @After + public void after() { + ourServerRule.getRestfulServer().unregisterProvider(myPartitionManagementProvider); + } + + @Test + public void testAddPartition() { + when(myPartitionConfigSvc.createPartition(any())).thenAnswer(createAnswer()); + + Parameters input = new Parameters(); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123)); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123")); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new StringType("a description")); + ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); + + Parameters response = myClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION) + .withParameters(input) + .encodedXml() + .execute(); + + ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response)); + verify(myPartitionConfigSvc, times(1)).createPartition(any()); + verifyNoMoreInteractions(myPartitionConfigSvc); + + assertEquals(123, ((IntegerType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID)).getValue().intValue()); + assertEquals("PARTITION-123", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME)).getValue()); + assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue()); + } + + @Test + public void testUpdatePartition() { + when(myPartitionConfigSvc.updatePartition(any())).thenAnswer(createAnswer()); + + Parameters input = new Parameters(); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123)); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123")); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new StringType("a description")); + ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); + + Parameters response = myClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_UPDATE_PARTITION) + .withParameters(input) + .encodedXml() + .execute(); + + ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response)); + verify(myPartitionConfigSvc, times(1)).updatePartition(any()); + verifyNoMoreInteractions(myPartitionConfigSvc); + + assertEquals(123, ((IntegerType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID)).getValue().intValue()); + assertEquals("PARTITION-123", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME)).getValue()); + assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue()); + } + + @Test + public void testDeletePartition() { + Parameters input = new Parameters(); + input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123)); + ourLog.info("Input:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); + + Parameters response = myClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_DELETE_PARTITION) + .withParameters(input) + .encodedXml() + .execute(); + + ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response)); + verify(myPartitionConfigSvc, times(1)).deletePartition(eq(123)); + verifyNoMoreInteractions(myPartitionConfigSvc); + } + + @Configuration + public static class MyConfig { + + @Bean + public PartitionManagementProvider partitionManagementProvider() { + return new PartitionManagementProvider(); + } + + @Bean + public FhirContext fhirContext() { + return ourCtx; + } + + } + + @NotNull + private static Answer createAnswer() { + return t -> { + return t.getArgument(0, PartitionEntity.class); + }; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionSettingsSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionSettingsSvcImplTest.java new file mode 100644 index 00000000000..a37983dd655 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionSettingsSvcImplTest.java @@ -0,0 +1,173 @@ +package ca.uhn.fhir.jpa.partition; + +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class PartitionSettingsSvcImplTest extends BaseJpaR4Test { + + @Test + public void testCreateAndFetchPartition() { + + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME123"); + partition.setDescription("A description"); + myPartitionConfigSvc.createPartition(partition); + + partition = myPartitionConfigSvc.getPartitionById(123); + assertEquals("NAME123", partition.getName()); + + partition = myPartitionConfigSvc.getPartitionByName("NAME123"); + assertEquals("NAME123", partition.getName()); + } + + @Test + public void testDeletePartition() { + + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME123"); + partition.setDescription("A description"); + myPartitionConfigSvc.createPartition(partition); + + partition = myPartitionConfigSvc.getPartitionById(123); + assertEquals("NAME123", partition.getName()); + + myPartitionConfigSvc.deletePartition(123); + + try { + myPartitionConfigSvc.getPartitionById(123); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("No partition exists with ID 123", e.getMessage()); + } + + } + + @Test + public void testDeletePartition_TryToDeleteDefault() { + + try { + myPartitionConfigSvc.deletePartition(0); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Can not delete default partition", e.getMessage()); + } + + } + + @Test + public void testUpdatePartition_TryToUseExistingName() { + + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME123"); + partition.setDescription("A description"); + myPartitionConfigSvc.createPartition(partition); + + partition = new PartitionEntity(); + partition.setId(111); + partition.setName("NAME111"); + partition.setDescription("A description"); + myPartitionConfigSvc.createPartition(partition); + + partition = new PartitionEntity(); + partition.setId(111); + partition.setName("NAME123"); + partition.setDescription("A description"); + try { + myPartitionConfigSvc.updatePartition(partition); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Partition name \"NAME123\" is already defined", e.getMessage()); + } + } + + @Test + public void testUpdatePartition_TryToRenameDefault() { + PartitionEntity partition = new PartitionEntity(); + partition.setId(0); + partition.setName("NAME123"); + partition.setDescription("A description"); + try { + myPartitionConfigSvc.updatePartition(partition); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Can not rename default partition", e.getMessage()); + } + } + + @Test + public void testUpdatePartition() { + + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME123"); + partition.setDescription("A description"); + myPartitionConfigSvc.createPartition(partition); + + partition = myPartitionConfigSvc.getPartitionById(123); + assertEquals("NAME123", partition.getName()); + + partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME-NEW"); + partition.setDescription("A description"); + myPartitionConfigSvc.updatePartition(partition); + + partition = myPartitionConfigSvc.getPartitionById(123); + assertEquals("NAME-NEW", partition.getName()); + } + + @Test + public void testCreatePartition_InvalidName() { + + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME 123"); + partition.setDescription("A description"); + try { + myPartitionConfigSvc.createPartition(partition); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Partition name \"NAME 123\" is not valid", e.getMessage()); + } + + } + + @Test + public void testCreatePartition_0Blocked() { + PartitionEntity partition = new PartitionEntity(); + partition.setId(0); + partition.setName("NAME123"); + partition.setDescription("A description"); + try { + myPartitionConfigSvc.createPartition(partition); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Can not create a partition with ID 0 (this is a reserved value)", e.getMessage()); + } + + } + + @Test + public void testUpdatePartition_UnknownPartitionBlocked() { + PartitionEntity partition = new PartitionEntity(); + partition.setId(123); + partition.setName("NAME123"); + partition.setDescription("A description"); + try { + myPartitionConfigSvc.updatePartition(partition); + fail(); + } catch (InvalidRequestException e) { + assertEquals("No partition exists with ID 123", e.getMessage()); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java index 93ce55c988d..15ea7c88cf6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider; -import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.model.dstu2.resource.Bundle; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index e2fc66fd728..8162df8355a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -58,14 +58,13 @@ import org.springframework.test.util.AopTestUtils; import com.google.common.base.Charsets; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; @@ -92,11 +91,8 @@ import ca.uhn.fhir.model.dstu2.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.resource.Practitioner; -import ca.uhn.fhir.model.dstu2.resource.Questionnaire; -import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse; import ca.uhn.fhir.model.dstu2.resource.Subscription; import ca.uhn.fhir.model.dstu2.resource.ValueSet; -import ca.uhn.fhir.model.dstu2.valueset.AnswerFormatEnum; import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum; import ca.uhn.fhir.model.dstu2.valueset.EncounterStateEnum; @@ -2713,7 +2709,6 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info("Response: {}", responseString); assertThat(responseString, not(containsString("Resource has no id"))); - assertEquals(200, response.getStatusLine().getStatusCode()); } finally { IOUtils.closeQuietly(response); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java index 1e2e511427d..731b5e96b93 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.util.ExpungeOptions; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum; @@ -16,7 +16,8 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class ResourceProviderExpungeDstu2Test extends BaseResourceProviderDstu2Test { private IIdType myOneVersionPatientId; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java index b1e4d7e7f72..a19c766dd19 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.rp.dstu2.ObservationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.OrganizationResourceProvider; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java index 9b74dbdb779..22a625b873a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java @@ -1,14 +1,14 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -51,7 +51,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { - protected static JpaValidationSupportChainDstu3 myValidationSupport; + protected static IValidationSupport myValidationSupport; protected static IGenericClient ourClient; protected static CloseableHttpClient ourHttpClient; protected static int ourPort; @@ -61,8 +61,8 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { protected static SearchParamRegistryImpl ourSearchParamRegistry; protected static DatabaseBackedPagingProvider ourPagingProvider; protected static ISearchCoordinatorSvc ourSearchCoordinatorSvc; - private static Server ourServer; protected static SubscriptionTriggeringProvider ourSubscriptionTriggeringProvider; + private static Server ourServer; public BaseResourceProviderDstu3Test() { super(); @@ -75,7 +75,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { ourRestServer.getInterceptorService().unregisterAllInterceptors(); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) @Before public void before() throws Exception { myResourceCountsCache.clear(); @@ -97,7 +97,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class)); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig); + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry); confProvider.setImplementationDescription("THIS IS THE DESC"); ourRestServer.setServerConformanceProvider(confProvider); @@ -123,8 +123,8 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { ServletHolder subsServletHolder = new ServletHolder(); subsServletHolder.setServlet(dispatcherServlet); subsServletHolder.setInitParameter( - ContextLoader.CONFIG_LOCATION_PARAM, - WebsocketDispatcherConfig.class.getName()); + ContextLoader.CONFIG_LOCATION_PARAM, + WebsocketDispatcherConfig.class.getName()); proxyHandler.addServlet(subsServletHolder, "/*"); // Register a CORS filter @@ -147,21 +147,23 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { server.setHandler(proxyHandler); JettyUtil.startServer(server); - ourPort = JettyUtil.getPortForStartedServer(server); - ourServerBase = "http://localhost:" + ourPort + "/fhir/context"; + ourPort = JettyUtil.getPortForStartedServer(server); + ourServerBase = "http://localhost:" + ourPort + "/fhir/context"; WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); - myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class); + myValidationSupport = wac.getBean(IValidationSupport.class); ourSearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class); ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class); + confProvider.setSearchParamRegistry(ourSearchParamRegistry); + myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase); if (shouldLogClient()) { ourClient.registerInterceptor(new LoggingInterceptor()); } - + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); HttpClientBuilder builder = HttpClientBuilder.create(); builder.setConnectionManager(connectionManager); @@ -196,7 +198,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { ourHttpClient.close(); ourServer = null; ourHttpClient = null; - myValidationSupport.flush(); + myValidationSupport.invalidateCaches(); myValidationSupport = null; ourWebApplicationContext.close(); ourWebApplicationContext = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java index 5d0269999b1..6ed15342fb3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java index 354a3ee3412..d9b9ac77957 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java index 482c12cfb20..ca6c479698c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -167,60 +167,6 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv } - @Test - public void testConformanceOverrideNotAllowed() { - myModelConfig.setDefaultSearchParamsCanBeOverridden(false); - - CapabilityStatement conformance = ourClient - .fetchConformance() - .ofType(CapabilityStatement.class) - .execute(); - Map map = extractSearchParams(conformance, "Patient"); - - CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); - assertNull(param); - - param = map.get("gender"); - assertNotNull(param); - - // Add a custom search parameter - SearchParameter fooSp = new SearchParameter(); - fooSp.addBase("Patient"); - fooSp.setCode("foo"); - fooSp.setName("foo"); - fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); - fooSp.setTitle("FOO SP"); - fooSp.setExpression("Patient.gender"); - fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); - fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); - mySearchParameterDao.create(fooSp, mySrd); - - // Disable an existing parameter - fooSp = new SearchParameter(); - fooSp.addBase("Patient"); - fooSp.setCode("gender"); - fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); - fooSp.setTitle("Gender"); - fooSp.setExpression("Patient.gender"); - fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); - fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED); - mySearchParameterDao.create(fooSp, mySrd); - - mySearchParamRegistry.forceRefresh(); - - conformance = ourClient - .capabilities() - .ofType(CapabilityStatement.class) - .execute(); - map = extractSearchParams(conformance, "Patient"); - - param = map.get("foo"); - assertEquals("foo", param.getName()); - - param = map.get("gender"); - assertNotNull(param); - - } @Test public void testCreatingParamMarksCorrectResourcesForReindexing() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3DistanceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3DistanceTest.java new file mode 100644 index 00000000000..75f492e45da --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3DistanceTest.java @@ -0,0 +1,153 @@ +package ca.uhn.fhir.jpa.provider.dstu3; + +import ca.uhn.fhir.jpa.util.CoordCalculatorTest; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Location; +import org.hl7.fhir.dstu3.model.PractitionerRole; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.Test; + +import java.net.URLEncoder; + +import static org.junit.Assert.assertEquals; + +public class ResourceProviderDstu3DistanceTest extends BaseResourceProviderDstu3Test { + + @Override + public void before() throws Exception { + super.before(); + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Test + public void testNearSearchApproximate() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + IIdType locId = ourClient.create().resource(loc).execute().getId().toUnqualifiedVersionless(); + + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + bigEnoughDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(locId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + Location.SP_NEAR_DISTANCE + "=" + tooSmallDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(0, actual.getEntry().size()); + } + } + + @Test + public void testNearSearchDistanceNoDistanceChained() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LONGITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + IIdType locId = ourClient.create().resource(loc).execute().getId().toUnqualifiedVersionless(); + + PractitionerRole pr = new PractitionerRole(); + pr.addLocation().setReference(locId.getValue()); + IIdType prId = ourClient.create().resource(pr).execute().getId().toUnqualifiedVersionless(); + + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + latitude + URLEncoder.encode(":") + longitude; + + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(prId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + + @Test + public void testNearSearchApproximateChained() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + myCaptureQueriesListener.clear(); + IIdType locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless(); + myCaptureQueriesListener.logInsertQueries(); + + PractitionerRole pr = new PractitionerRole(); + pr.addLocation().setReference(locId.getValue()); + IIdType prId = myPractitionerRoleDao.create(pr).getId().toUnqualifiedVersionless(); + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + "location." + Location.SP_NEAR_DISTANCE + "=" + bigEnoughDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(prId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + URLEncoder.encode(":") + CoordCalculatorTest.LONGITUDE_CHIN + + "&" + + "location." + Location.SP_NEAR_DISTANCE + "=" + tooSmallDistance + URLEncoder.encode("|http://unitsofmeasure.org|km"); + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(0, actual.getEntry().size()); + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 45edc4928c7..208fbf64bbd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -1,11 +1,10 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.provider.r4.ResourceProviderR4Test; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; -import ca.uhn.fhir.jpa.util.CoordCalculatorTest; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; @@ -36,7 +35,7 @@ import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.*; import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent; @@ -66,7 +65,6 @@ import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -164,7 +162,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { @Test public void testExtensionUrlWithHl7UrlPost() throws IOException { - ValueSet vs = myValidationSupport.fetchResource(myFhirCtx, ValueSet.class, "http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode"); + ValueSet vs = myValidationSupport.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode"); myValueSetDao.create(vs); @@ -348,7 +346,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { .returnBundle(Bundle.class) .execute(); } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Invalid resource type: FOO", e.getMessage()); + assertEquals("HTTP 400 Bad Request: Invalid/unsupported resource type: \"FOO\"", e.getMessage()); } } @@ -816,15 +814,13 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myDaoConfig.setAllowMultipleDelete(true); - //@formatter:off - IBaseOperationOutcome response = ourClient + MethodOutcome response = ourClient .delete() .resourceConditionalByType(Patient.class) .where(Patient.IDENTIFIER.exactly().code(methodName)) .execute(); - //@formatter:on - String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response); + String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response.getOperationOutcome()); ourLog.info(encoded); assertThat(encoded, containsString( " mySubscriptionRegistry.size()); Thread.sleep(500); } @@ -225,7 +212,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { ourHttpClient.close(); ourServer = null; ourHttpClient = null; - myValidationSupport.flush(); + myValidationSupport.invalidateCaches(); myValidationSupport = null; ourWebApplicationContext.close(); ourWebApplicationContext = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java index 570c9a3a061..8d8c4b2dfcc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -26,7 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java index 15c9c00db8e..9292d8b16da 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java @@ -3,11 +3,10 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.DocumentReference; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CascadingDeleteInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CascadingDeleteInterceptorR4Test.java index 0e447601bb3..19ebdc194b4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CascadingDeleteInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CascadingDeleteInterceptorR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; @@ -45,7 +45,7 @@ public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test public void before() throws Exception { super.before(); - myDeleteInterceptor = new CascadingDeleteInterceptor(myDaoRegistry, myInterceptorBroadcaster); + myDeleteInterceptor = new CascadingDeleteInterceptor(myFhirCtx, myDaoRegistry, myInterceptorBroadcaster); } @Override @@ -100,6 +100,20 @@ public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test } } + @Test + public void testDeleteWithNoRequestObject() { + createResources(); + + myInterceptorRegistry.registerInterceptor(myDeleteInterceptor); + + try { + myPatientDao.delete(myPatientId); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage(), containsString("because at least one resource has a reference to this resource")); + } + } + @Test public void testDeleteWithInterceptorAndConstraints() { createResources(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java index d150f795862..97463c51ce9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java index ae4b221e433..848561b02d5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java index 443775022ab..6d1d76ac695 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider; import ca.uhn.fhir.jpa.rp.r4.OrganizationResourceProvider; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java index 7d35ccc3046..69d92c21254 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java @@ -1,12 +1,12 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.ExpungeOptions; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/MultitenantServerR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/MultitenantServerR4Test.java new file mode 100644 index 00000000000..7235561d227 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/MultitenantServerR4Test.java @@ -0,0 +1,157 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.util.ProviderConstants; +import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; +import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; +import ca.uhn.fhir.jpa.util.TestUtil; +import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; +import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.DEFAULT_PERSISTED_PARTITION_NAME; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@SuppressWarnings("Duplicates") +public class MultitenantServerR4Test extends BaseResourceProviderR4Test { + + @Autowired + private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor; + @Autowired + private PartitionManagementProvider myPartitionManagementProvider; + + private CapturingInterceptor myCapturingInterceptor; + private UrlTenantSelectionInterceptor myTenantInterceptor; + + @Override + @Before + public void before() throws Exception { + super.before(); + + myPartitionSettings.setPartitioningEnabled(true); + ourRestServer.registerInterceptor(myRequestTenantPartitionInterceptor); + ourRestServer.registerProvider(myPartitionManagementProvider); + ourRestServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); + + myCapturingInterceptor = new CapturingInterceptor(); + ourClient.getInterceptorService().registerInterceptor(myCapturingInterceptor); + + myTenantInterceptor = new UrlTenantSelectionInterceptor(); + ourClient.getInterceptorService().registerInterceptor(myTenantInterceptor); + + createTenants(); + } + + @Override + @After + public void after() throws Exception { + super.after(); + + myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled()); + ourRestServer.unregisterInterceptor(myRequestTenantPartitionInterceptor); + ourRestServer.unregisterProvider(myPartitionManagementProvider); + ourRestServer.setTenantIdentificationStrategy(null); + + ourClient.getInterceptorService().unregisterAllInterceptors(); + } + + @Override + protected boolean shouldLogClient() { + return true; + } + + @Test + public void testFetchCapabilityStatement() { + myTenantInterceptor.setTenantId("TENANT-A"); + CapabilityStatement cs = ourClient.capabilities().ofType(CapabilityStatement.class).execute(); + + assertEquals("HAPI FHIR Server", cs.getSoftware().getName()); + assertEquals(ourServerBase + "/TENANT-A/metadata", myCapturingInterceptor.getLastRequest().getUri()); + } + + @Test + public void testCreateAndRead() { + + myTenantInterceptor.setTenantId("TENANT-A"); + Patient patientA = new Patient(); + patientA.setActive(true); + IIdType idA = ourClient.create().resource(patientA).execute().getId().toUnqualifiedVersionless(); + + myTenantInterceptor.setTenantId("TENANT-B"); + Patient patientB = new Patient(); + patientB.setActive(true); + ourClient.create().resource(patientB).execute(); + + // Now read back + + myTenantInterceptor.setTenantId("TENANT-A"); + Patient response = ourClient.read().resource(Patient.class).withId(idA).execute(); + assertTrue(response.getActive()); + + myTenantInterceptor.setTenantId("TENANT-B"); + try { + ourClient.read().resource(Patient.class).withId(idA).execute(); + fail(); + } catch (ResourceNotFoundException e) { + // good + } + } + + @Test + public void testCreate_InvalidTenant() { + + myTenantInterceptor.setTenantId("TENANT-ZZZ"); + Patient patientA = new Patient(); + patientA.setActive(true); + try { + ourClient.create().resource(patientA).execute(); + fail(); + } catch (ResourceNotFoundException e) { + assertThat(e.getMessage(), containsString("Unknown partition name: TENANT-ZZZ")); + } + + } + + private void createTenants() { + myTenantInterceptor.setTenantId(DEFAULT_PERSISTED_PARTITION_NAME); + + ourClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION) + .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1)) + .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A")) + .execute(); + + ourClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION) + .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2)) + .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B")) + .execute(); + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java index c47f1c4f461..248791983b3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java @@ -18,7 +18,7 @@ import org.junit.*; import com.google.common.base.Charsets; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java index ad0aae3076d..29da7c09c66 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -38,7 +38,6 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; -import javax.persistence.Query; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -183,61 +182,6 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide } - @Test - public void testConformanceOverrideNotAllowed() { - myModelConfig.setDefaultSearchParamsCanBeOverridden(false); - - CapabilityStatement conformance = ourClient - .fetchConformance() - .ofType(CapabilityStatement.class) - .execute(); - Map map = extractSearchParams(conformance, "Patient"); - - CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); - assertNull(param); - - param = map.get("gender"); - assertNotNull(param); - - // Add a custom search parameter - SearchParameter fooSp = new SearchParameter(); - fooSp.addBase("Patient"); - fooSp.setCode("foo"); - fooSp.setName("foo"); - fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); - fooSp.setTitle("FOO SP"); - fooSp.setExpression("Patient.gender"); - fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); - fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE); - mySearchParameterDao.create(fooSp, mySrd); - - // Disable an existing parameter - fooSp = new SearchParameter(); - fooSp.addBase("Patient"); - fooSp.setCode("gender"); - fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN); - fooSp.setTitle("Gender"); - fooSp.setExpression("Patient.gender"); - fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL); - fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.RETIRED); - mySearchParameterDao.create(fooSp, mySrd); - - mySearchParamRegistry.forceRefresh(); - - conformance = ourClient - .fetchConformance() - .ofType(CapabilityStatement.class) - .execute(); - map = extractSearchParams(conformance, "Patient"); - - param = map.get("foo"); - assertEquals("foo", param.getName()); - - param = map.get("gender"); - assertNotNull(param); - - } - @Test public void testCreatingParamMarksCorrectResourcesForReindexing() { Patient pat = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java index c41bb4160f5..de83fbc8689 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java @@ -1,20 +1,25 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class ResourceProviderExpungeR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java index 180d8483ed8..fccd7b0d6eb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; @@ -46,8 +46,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -59,9 +59,6 @@ import static org.mockito.Mockito.*; public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderInterceptorR4Test.class); - private IServerOperationInterceptor myDaoInterceptor; - - private IServerOperationInterceptor myServerInterceptor; private List myInterceptors = new ArrayList<>(); @Mock private IAnonymousInterceptor myHook; @@ -74,47 +71,11 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes super.after(); myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); - ourRestServer.unregisterInterceptor(myServerInterceptor); - - myInterceptorRegistry.unregisterInterceptors(myInterceptors); - myInterceptors.clear(); - } - - @Override - public void before() throws Exception { - super.before(); - - myServerInterceptor = mock(IServerOperationInterceptor.class); - myDaoInterceptor = mock(IServerOperationInterceptor.class); - - resetServerInterceptor(); - - ourRestServer.registerInterceptor(myServerInterceptor); - - ourRestServer.registerInterceptor(new InterceptorAdapter() { - @Override - public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) { - super.incomingRequestPreHandled(theOperation, theProcessedRequest); - } - }); - - } - - private void resetServerInterceptor() throws ServletException, IOException { - reset(myServerInterceptor); - reset(myDaoInterceptor); - when(myServerInterceptor.handleException(any(RequestDetails.class), any(BaseServerResponseException.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myServerInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myServerInterceptor.outgoingResponse(any(RequestDetails.class))).thenReturn(true); - when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true); - when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + ourRestServer.getInterceptorService().unregisterAllInterceptors(); } @Test - public void testPerfInterceptors() throws InterruptedException { + public void testPerfInterceptors() { myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(15, 100)); for (int i = 0; i < 30; i++) { Patient p = new Patient(); @@ -124,11 +85,11 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes } IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); - myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED, interceptor); - myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE, interceptor); - myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_FAILED, interceptor); - myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE, interceptor); - myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_FAILED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE, interceptor); myInterceptors.add(interceptor); myInterceptors.add(new PerformanceTracingLoggingInterceptor()); @@ -149,6 +110,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes // Load the next (and final) page reset(interceptor); results = ourClient.loadPage().next(results).execute(); + assertNotNull(results); verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture()); verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture()); verify(interceptor, times(1)).invoke(eq(Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture()); @@ -177,7 +139,12 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes transaction(bundle); // Do it again but with a conditional create that shouldn't actually create - resetServerInterceptor(); + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, interceptor); + entry.getRequest().setIfNoneExist("Patient?name=" + methodName); transaction(bundle); @@ -185,22 +152,12 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes * Server Interceptor */ - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - verify(myServerInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); - assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(1)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED), myParamsCaptor.capture()); + assertEquals(RestOperationTypeEnum.TRANSACTION, myParamsCaptor.getAllValues().get(0).get(RestOperationTypeEnum.class)); - verify(myServerInterceptor, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myServerInterceptor, times(0)).resourceCreated(any(RequestDetails.class), any(IBaseResource.class)); - verify(myServerInterceptor, times(0)).resourceUpdated(any(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); - - /* - * DAO Interceptor - */ - - verify(myDaoInterceptor, times(0)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myDaoInterceptor, times(0)).resourceCreated(any(RequestDetails.class), any(IBaseResource.class)); - verify(myDaoInterceptor, times(0)).resourceUpdated(any(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); + verify(interceptor, times(1)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), myParamsCaptor.capture()); } @@ -212,8 +169,8 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes pt.addName().setFamily(methodName); String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); - verify(myServerInterceptor, times(0)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); - verify(myDaoInterceptor, times(0)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, interceptor); HttpPost post = new HttpPost(ourServerBase + "/Patient"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); @@ -225,12 +182,9 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); } - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - verify(myServerInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); - assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getValue()); - assertEquals("Patient", ardCaptor.getValue().getResourceType()); - assertNotNull(ardCaptor.getValue().getResource()); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(1)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED), myParamsCaptor.capture()); + assertEquals(RestOperationTypeEnum.CREATE, myParamsCaptor.getValue().get(RestOperationTypeEnum.class)); + assertEquals("Patient", myParamsCaptor.getValue().get(RequestDetails.class).getResource().getIdElement().getResourceType()); } @@ -249,27 +203,18 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes entry.getRequest().setMethod(HTTPVerb.POST); entry.getRequest().setUrl("Patient"); + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, interceptor); + transaction(bundle); - /* - * Server Interceptor - */ - - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); - assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); - assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType()); - assertNotNull(ardCaptor.getAllValues().get(0).getResource()); - assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1)); - assertEquals("Patient", ardCaptor.getAllValues().get(1).getResourceType()); - assertNotNull(ardCaptor.getAllValues().get(1).getResource()); - - ArgumentCaptor rdCaptor = ArgumentCaptor.forClass(RequestDetails.class); - ArgumentCaptor srCaptor = ArgumentCaptor.forClass(HttpServletRequest.class); - ArgumentCaptor sRespCaptor = ArgumentCaptor.forClass(HttpServletResponse.class); - verify(myServerInterceptor, times(1)).incomingRequestPostProcessed(rdCaptor.capture(), srCaptor.capture(), sRespCaptor.capture()); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(2)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED), myParamsCaptor.capture()); + assertEquals(RestOperationTypeEnum.CREATE, myParamsCaptor.getValue().get(RestOperationTypeEnum.class)); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(1)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED), myParamsCaptor.capture()); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(1)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED), myParamsCaptor.capture()); } @Test @@ -307,8 +252,6 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes IIdType orgId = ourClient.create().resource(org).execute().getId().toUnqualified(); assertNotNull(orgId.getVersionIdPartAsLong()); - resetServerInterceptor(); - Patient pt = new Patient(); pt.addName().setFamily(methodName); pt.setManagingOrganization(new Reference(orgId)); @@ -320,8 +263,8 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes ourLog.info(resource); - verify(myServerInterceptor, times(0)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); - verify(myDaoInterceptor, times(0)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, interceptor); HttpPost post = new HttpPost(ourServerBase + "/Patient"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); @@ -333,16 +276,10 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); } - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - verify(myServerInterceptor, times(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(1)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED), myParamsCaptor.capture()); + assertEquals(RestOperationTypeEnum.CREATE, myParamsCaptor.getValue().get(RestOperationTypeEnum.class)); - assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getValue()); - assertEquals("Patient", ardCaptor.getValue().getResourceType()); - assertNotNull(ardCaptor.getValue().getResource()); - - Patient patient; - patient = (Patient) ardCaptor.getAllValues().get(0).getResource(); + Patient patient = (Patient) myParamsCaptor.getValue().get(RequestDetails.class).getResource(); assertEquals(orgId.getValue(), patient.getManagingOrganization().getReference()); } @@ -367,31 +304,19 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes transaction(bundle); // Do it again but with an update that shouldn't actually create - resetServerInterceptor(); + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, interceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, interceptor); + entry.getRequest().setIfNoneExist("Patient?name=" + methodName); transaction(bundle); - /* - * Server Interceptor - */ - - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); - assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); - assertEquals(RestOperationTypeEnum.UPDATE, opTypeCaptor.getAllValues().get(1)); - - verify(myServerInterceptor, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myServerInterceptor, times(0)).resourceCreated(any(RequestDetails.class), any(IBaseResource.class)); - verify(myServerInterceptor, times(0)).resourceUpdated(any(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); - - /* - * DAO Interceptor - */ - - verify(myDaoInterceptor, times(0)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myDaoInterceptor, times(0)).resourceCreated(any(RequestDetails.class), any(IBaseResource.class)); - verify(myDaoInterceptor, times(0)).resourceUpdated(any(RequestDetails.class), any(IBaseResource.class), any(IBaseResource.class)); + verify(interceptor, timeout(Duration.ofSeconds(10)).times(2)).invoke(eq(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED), myParamsCaptor.capture()); + assertEquals(RestOperationTypeEnum.TRANSACTION, myParamsCaptor.getAllValues().get(0).get(RestOperationTypeEnum.class)); + assertEquals(RestOperationTypeEnum.UPDATE, myParamsCaptor.getAllValues().get(1).get(RestOperationTypeEnum.class)); + verify(interceptor, times(0)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED), any()); + verify(interceptor, times(0)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED), any()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderOnlySomeResourcesProvidedR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderOnlySomeResourcesProvidedR4Test.java index 5244c39af22..ea4e4c52bea 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderOnlySomeResourcesProvidedR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderOnlySomeResourcesProvidedR4Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.r4.model.Patient; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CacheTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CacheTest.java index ca08514295b..85cd89e30f7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CacheTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CacheTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -9,20 +9,29 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Patient; import org.junit.After; import org.junit.AfterClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.Date; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class ResourceProviderR4CacheTest extends BaseResourceProviderR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(ResourceProviderR4CacheTest.class); private CapturingInterceptor myCapturingInterceptor; @Autowired private ISearchDao mySearchEntityDao; @@ -184,6 +193,39 @@ public class ResourceProviderR4CacheTest extends BaseResourceProviderR4Test { assertEquals(results1.getId(), results2.getId()); } + @Test + public void testDeletedSearchResultsNotReturnedFromCache() { + Patient p = new Patient(); + p.addName().setFamily("Foo"); + String p1Id = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Foo"); + String p2Id = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + Bundle resp1 = ourClient + .search() + .forResource("Patient") + .where(Patient.NAME.matches().value("foo")) + .returnBundle(Bundle.class) + .execute(); + assertEquals(2, resp1.getEntry().size()); + + ourClient.delete().resourceById(new IdType(p1Id)).execute(); + + Bundle resp2 = ourClient + .search() + .forResource("Patient") + .where(Patient.NAME.matches().value("foo")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals(resp1.getId(), resp2.getId()); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp2)); + assertEquals(1, resp2.getEntry().size()); + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4DistanceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4DistanceTest.java new file mode 100644 index 00000000000..653f7b7a258 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4DistanceTest.java @@ -0,0 +1,146 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.jpa.util.CoordCalculatorTest; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Location; +import org.hl7.fhir.r4.model.PractitionerRole; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ResourceProviderR4DistanceTest extends BaseResourceProviderR4Test { + @Override + public void before() throws Exception { + super.before(); + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + + @Test + public void testNearSearchApproximate() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + IIdType locId = ourClient.create().resource(loc).execute().getId().toUnqualifiedVersionless(); + + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + CoordCalculatorTest.LONGITUDE_CHIN + + "|" + bigEnoughDistance; + + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(locId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + String url = "/Location?" + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + CoordCalculatorTest.LONGITUDE_CHIN + + "|" + tooSmallDistance; + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(0, actual.getEntry().size()); + } + } + + @Test + public void testNearSearchDistanceNoDistanceChained() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_CHIN; + double longitude = CoordCalculatorTest.LONGITUDE_CHIN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + IIdType locId = ourClient.create().resource(loc).execute().getId().toUnqualifiedVersionless(); + + PractitionerRole pr = new PractitionerRole(); + pr.addLocation().setReference(locId.getValue()); + IIdType prId = ourClient.create().resource(pr).execute().getId().toUnqualifiedVersionless(); + + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + latitude + "|" + longitude; + + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(prId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + + @Test + public void testNearSearchApproximateChained() { + Location loc = new Location(); + double latitude = CoordCalculatorTest.LATITUDE_UHN; + double longitude = CoordCalculatorTest.LONGITUDE_UHN; + Location.LocationPositionComponent position = new Location.LocationPositionComponent().setLatitude(latitude).setLongitude(longitude); + loc.setPosition(position); + myCaptureQueriesListener.clear(); + IIdType locId = myLocationDao.create(loc).getId().toUnqualifiedVersionless(); + myCaptureQueriesListener.logInsertQueries(); + + PractitionerRole pr = new PractitionerRole(); + pr.addLocation().setReference(locId.getValue()); + IIdType prId = myPractitionerRoleDao.create(pr).getId().toUnqualifiedVersionless(); + { // In the box + double bigEnoughDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN * 2; + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + CoordCalculatorTest.LONGITUDE_CHIN + + "|" + bigEnoughDistance; + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(1, actual.getEntry().size()); + assertEquals(prId.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); + } + + { // Outside the box + double tooSmallDistance = CoordCalculatorTest.DISTANCE_KM_CHIN_TO_UHN / 2; + String url = "PractitionerRole?location." + + Location.SP_NEAR + "=" + CoordCalculatorTest.LATITUDE_CHIN + "|" + CoordCalculatorTest.LONGITUDE_CHIN + + "|" + tooSmallDistance; + + myCaptureQueriesListener.clear(); + Bundle actual = ourClient + .search() + .byUrl(ourServerBase + "/" + url) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); + myCaptureQueriesListener.logSelectQueries(); + + assertEquals(0, actual.getEntry().size()); + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index a8f485ba05d..f771a4a862a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; @@ -15,6 +15,7 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.client.apache.ResourceEntity; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; @@ -41,7 +42,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.*; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.*; import org.hl7.fhir.r4.model.Encounter.EncounterLocationComponent; @@ -52,6 +53,7 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; @@ -403,15 +405,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertThat(idValues, contains(pid)); // Search param on extension - myCaptureQueriesListener.clear(); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg=" + orgId.getValue()); - myCaptureQueriesListener.logSelectQueries(); assertThat(idValues, contains(pid)); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.name=ORGANIZATION"); assertThat(idValues, contains(pid)); + myCaptureQueriesListener.clear(); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.name=PARENT"); + myCaptureQueriesListener.logSelectQueries(); assertThat(idValues, contains(pid)); idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT"); @@ -1255,15 +1257,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myDaoConfig.setAllowMultipleDelete(true); - //@formatter:off - IBaseOperationOutcome response = ourClient + MethodOutcome response = ourClient .delete() .resourceConditionalByType(Patient.class) .where(Patient.IDENTIFIER.exactly().code(methodName)) .execute(); - //@formatter:on - String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response); + String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response.getOperationOutcome()); ourLog.info(encoded); assertThat(encoded, containsString( "AA")).setStatus(Narrative.NarrativeStatus.GENERATED); + input.getMeta().addProfile("http://foo/structuredefinition/myprofile"); + + input.getCode().setText("Hello"); + input.setStatus(ObservationStatus.FINAL); + + HttpPost post = new HttpPost(ourServerBase + "/Observation/$validate?_pretty=true"); + post.setEntity(new ResourceEntity(myFhirCtx, input)); + + try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { + String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); + ourLog.info(respString); + assertEquals(412, resp.getStatusLine().getStatusCode()); + assertThat(respString, containsString("Profile reference \\\"http://foo/structuredefinition/myprofile\\\" could not be resolved, so has not been checked")); + } + } + + @SuppressWarnings("unused") @Test public void testFullTextSearch() throws Exception { @@ -5344,7 +5366,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { @Test public void testValidateJsonWithDuplicateKey() throws IOException { - String inputStr = "{\"resourceType\":\"Patient\", \"name\":[{\"text\":foo\"}], name:[{\"text\":\"foo\"}] }"; + String inputStr = "{\"resourceType\":\"Patient\", \"name\":[{\"text\":\"foo\"}], \"name\":[{\"text\":\"foo\"}] }"; HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_JSON_NEW, "UTF-8"))); @@ -5354,7 +5376,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(resp); assertEquals(412, response.getStatusLine().getStatusCode()); - assertThat(resp, stringContainsInOrder("Error parsing JSON source: Syntax error in json reading special word false at Line 1")); + assertThat(resp, stringContainsInOrder("Duplicated property name: name")); } finally { response.getEntity().getContent().close(); response.close(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index ef683ba265f..167b14781f8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -1,10 +1,15 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; +import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; @@ -41,8 +46,16 @@ import java.util.Optional; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @@ -950,9 +963,9 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { .operation() .onType(ValueSet.class) .named("validate-code") - .withParameter(Parameters.class, "code", new StringType("Y")) - .andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/yesnodontknow")) - .andParameter("system", new StringType("http://terminology.hl7.org/CodeSystem/v2-0136")) + .withParameter(Parameters.class, "code", new StringType("male")) + .andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/administrative-gender")) + .andParameter("system", new StringType("http://hl7.org/fhir/administrative-gender")) .useHttpGet() .execute(); @@ -966,7 +979,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Yes", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderSummaryModeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderSummaryModeR4Test.java index 8bc3f634b9a..ff4314f069f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderSummaryModeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderSummaryModeR4Test.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; @@ -15,8 +14,6 @@ import org.junit.AfterClass; import org.junit.Test; import org.springframework.test.util.AopTestUtils; -import java.util.ArrayList; - import static org.junit.Assert.assertEquals; @SuppressWarnings("Duplicates") diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java index bb44312788c..4cd4ded20a5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerR4Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -23,7 +23,9 @@ import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; public class ServerR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java index 5c62e5deb4d..46f2779470d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.rp.r4.*; @@ -38,7 +39,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java index 9eda4cdcfc8..a36cb5e6cd9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.rp.r4.*; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java index b9c8f576308..576d5709362 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java @@ -1,17 +1,17 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -56,7 +56,7 @@ import static org.junit.Assert.fail; public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { - protected static JpaValidationSupportChainR5 myValidationSupport; + protected static IValidationSupport myValidationSupport; protected static CloseableHttpClient ourHttpClient; protected static int ourPort; protected static RestfulServer ourRestServer; @@ -109,7 +109,7 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class)); - JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(ourRestServer, mySystemDao, myDaoConfig); + JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry); confProvider.setImplementationDescription("THIS IS THE DESC"); ourRestServer.setServerConformanceProvider(confProvider); @@ -162,13 +162,13 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { ourServerBase = "http://localhost:" + ourPort + "/fhir/context"; WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); - myValidationSupport = wac.getBean(JpaValidationSupportChainR5.class); + myValidationSupport = wac.getBean(IValidationSupport.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class); ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class); - ourSubscriptionMatcherInterceptor.start(); myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); + confProvider.setSearchParamRegistry(ourSearchParamRegistry); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); HttpClientBuilder builder = HttpClientBuilder.create(); @@ -226,7 +226,7 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { ourHttpClient.close(); ourServer = null; ourHttpClient = null; - myValidationSupport.flush(); + myValidationSupport.invalidateCaches(); myValidationSupport = null; ourWebApplicationContext.close(); ourWebApplicationContext = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java index 6d4a4f4760b..198eaffa900 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 346ac91dcf3..80bb2f99677 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -968,27 +968,51 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { @Test public void testValidateCodeAgainstBuiltInSystem() { - Parameters respParam = ourClient - .operation() - .onType(ValueSet.class) - .named("validate-code") - .withParameter(Parameters.class, "code", new StringType("Y")) - .andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/yesnodontknow")) - .andParameter("system", new StringType("http://terminology.hl7.org/CodeSystem/v2-0136")) - .useHttpGet() - .execute(); + // Good code and system, good valueset + { + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new StringType("male")) + .andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/administrative-gender")) + .andParameter("system", new StringType("http://hl7.org/fhir/administrative-gender")) + .useHttpGet() + .execute(); - String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); - ourLog.info(resp); + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); - assertEquals("result", respParam.getParameter().get(0).getName()); - assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals("message", respParam.getParameter().get(1).getName()); - assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); + assertEquals("message", respParam.getParameter().get(1).getName()); + assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); - assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Yes", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + } + // Good code and system, but not in specified valueset + { + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new StringType("male")) + .andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/marital-status")) + .andParameter("system", new StringType("http://hl7.org/fhir/administrative-gender")) + .useHttpGet() + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); + + assertEquals("message", respParam.getParameter().get(1).getName()); + assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("Code not found")); + } } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java index 65fb4c99a8c..37ea3293d16 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ServerR5Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -24,7 +24,9 @@ import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; public class ServerR5Test extends BaseResourceProviderR5Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java index 199c44b0598..3f344d549e5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java @@ -11,7 +11,7 @@ import org.junit.AfterClass; import org.junit.Test; import org.springframework.test.util.AopTestUtils; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderTest.java index c4e578ef62d..55b2c4dd7db 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.search; -import ca.uhn.fhir.jpa.dao.IDao; +import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -24,7 +24,7 @@ public class PersistedJpaBundleProviderTest { String searchUuid = "this is not a hat"; myDao = mock(IDao.class); mySearchBuilderFactory = mock(SearchBuilderFactory.class); - myPersistedJpaBundleProvider = new PersistedJpaBundleProvider(request, searchUuid, myDao, mySearchBuilderFactory); + myPersistedJpaBundleProvider = new PersistedJpaBundleProvider(request, searchUuid); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java index a622a7a4933..d18b5060ba7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java @@ -2,11 +2,19 @@ package ca.uhn.fhir.jpa.search; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IResultIterator; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperService; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -35,8 +43,15 @@ import org.springframework.data.domain.Pageable; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; +import javax.annotation.Nonnull; import javax.persistence.EntityManager; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -44,9 +59,26 @@ import java.util.concurrent.atomic.AtomicInteger; import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @SuppressWarnings({"unchecked"}) @RunWith(MockitoJUnitRunner.class) @@ -75,6 +107,10 @@ public class SearchCoordinatorSvcImplTest { private IInterceptorBroadcaster myInterceptorBroadcaster; @Mock private SearchBuilderFactory mySearchBuilderFactory; + @Mock + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; + @Mock + private IRequestPartitionHelperService myPartitionHelperSvc; @After public void after() { @@ -97,6 +133,8 @@ public class SearchCoordinatorSvcImplTest { mySvc.setDaoRegistryForUnitTest(myDaoRegistry); mySvc.setInterceptorBroadcasterForUnitTest(myInterceptorBroadcaster); mySvc.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory); + mySvc.setPersistedJpaBundleProviderFactoryForUnitTest(myPersistedJpaBundleProviderFactory); + mySvc.setRequestPartitionHelperService(myPartitionHelperSvc); DaoConfig daoConfig = new DaoConfig(); mySvc.setDaoConfigForUnitTest(daoConfig); @@ -105,16 +143,17 @@ public class SearchCoordinatorSvcImplTest { when(myTxManager.getTransaction(any())).thenReturn(mock(TransactionStatus.class)); - doAnswer(theInvocation -> { - PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) theInvocation.getArguments()[0]; - provider.setSearchCoordinatorSvc(mySvc); - provider.setPlatformTransactionManager(myTxManager); - provider.setSearchCacheSvc(mySearchCacheSvc); - provider.setEntityManager(myEntityManager); - provider.setContext(ourCtx); - provider.setInterceptorBroadcaster(myInterceptorBroadcaster); - return null; - }).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class)); + when(myPersistedJpaBundleProviderFactory.newInstanceFirstPage(nullable(RequestDetails.class), nullable(Search.class), nullable(SearchCoordinatorSvcImpl.SearchTask.class), nullable(ISearchBuilder.class))).thenAnswer(t->{ + RequestDetails requestDetails = t.getArgument(0, RequestDetails.class); + Search search = t.getArgument(1, Search.class); + SearchCoordinatorSvcImpl.SearchTask searchTask = t.getArgument(2, SearchCoordinatorSvcImpl.SearchTask.class); + ISearchBuilder searchBuilder = t.getArgument(3, ISearchBuilder.class); + PersistedJpaSearchFirstPageBundleProvider retVal = new PersistedJpaSearchFirstPageBundleProvider(search, searchTask, searchBuilder, requestDetails); + retVal.setTxManagerForUnitTest(myTxManager); + retVal.setSearchCoordinatorSvcForUnitTest(mySvc); + return retVal; + }); + } private List createPidSequence(int to) { @@ -145,7 +184,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(800); IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); @@ -160,7 +199,6 @@ public class SearchCoordinatorSvcImplTest { } - // TODO INTERMITTENT this test fails intermittently @Test public void testAsyncSearchLargeResultSetBigCountSameCoordinator() { List allResults = new ArrayList<>(); @@ -179,7 +217,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 1); - when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(any(), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); when(mySearchCacheSvc.save(any())).thenAnswer(t -> { @@ -188,6 +226,8 @@ public class SearchCoordinatorSvcImplTest { return search; }); + // Do all the stubbing before starting any work, since we want to avoid threading issues + IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); assertEquals(null, result.size()); @@ -259,7 +299,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 2); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -283,7 +323,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 500); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); @@ -328,7 +368,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(800); IResultIterator iter = new SlowIterator(pids.iterator(), 2); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); when(mySearchCacheSvc.save(any())).thenAnswer(t ->{ ourLog.info("Saving search"); return t.getArgument( 0, Search.class); @@ -359,7 +399,7 @@ public class SearchCoordinatorSvcImplTest { * Now call from a new bundle provider. This simulates a separate HTTP * client request coming in. */ - provider = new PersistedJpaBundleProvider(null, result.getUuid(), myCallingDao, mySearchBuilderFactory); + provider = newPersistedJpaBundleProvider(result.getUuid()); resources = provider.getResources(10, 20); assertEquals(10, resources.size()); assertEquals("20", resources.get(0).getIdElement().getValueAsString()); @@ -376,7 +416,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(100); SlowIterator iter = new SlowIterator(pids.iterator(), 2); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -437,13 +477,19 @@ public class SearchCoordinatorSvcImplTest { * Now call from a new bundle provider. This simulates a separate HTTP * client request coming in. */ - provider = new PersistedJpaBundleProvider(null, uuid, myCallingDao, mySearchBuilderFactory); + provider = newPersistedJpaBundleProvider(uuid); resources = provider.getResources(10, 20); assertEquals(10, resources.size()); assertEquals("20", resources.get(0).getIdElement().getValueAsString()); assertEquals("29", resources.get(9).getIdElement().getValueAsString()); - provider = new PersistedJpaBundleProvider(null, uuid, myCallingDao, mySearchBuilderFactory); + provider = new PersistedJpaBundleProvider(null, uuid); + provider.setTxManagerForUnitTest(myTxManager); + provider.setSearchCacheSvcForUnitTest(mySearchCacheSvc); + provider.setContext(ourCtx); + provider.setDaoRegistryForUnitTest(myDaoRegistry); + provider.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory); + provider.setSearchCoordinatorSvcForUnitTest(mySvc); resources = provider.getResources(20, 40); assertEquals(20, resources.size()); assertEquals("30", resources.get(0).getIdElement().getValueAsString()); @@ -452,6 +498,19 @@ public class SearchCoordinatorSvcImplTest { myExpectedNumberOfSearchBuildersCreated = 3; } + @Nonnull + private PersistedJpaBundleProvider newPersistedJpaBundleProvider(String theUuid) { + PersistedJpaBundleProvider provider; + provider = new PersistedJpaBundleProvider(null, theUuid); + provider.setTxManagerForUnitTest(myTxManager); + provider.setSearchCacheSvcForUnitTest(mySearchCacheSvc); + provider.setContext(ourCtx); + provider.setSearchBuilderFactoryForUnitTest(mySearchBuilderFactory); + provider.setDaoRegistryForUnitTest(myDaoRegistry); + provider.setSearchCoordinatorSvcForUnitTest(mySvc); + return provider; + } + @Test public void testSynchronousSearch() { SearchParameterMap params = new SearchParameterMap(); @@ -459,7 +518,7 @@ public class SearchCoordinatorSvcImplTest { params.add("name", new StringParam("ANAME")); List pids = createPidSequence(800); - when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator())); + when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(new ResultIterator(pids.iterator())); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -480,7 +539,7 @@ public class SearchCoordinatorSvcImplTest { params.add("name", new StringParam("ANAME")); List pids = createPidSequence(800); - when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class))).thenReturn(new ResultIterator(pids.iterator())); + when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(new ResultIterator(pids.iterator())); pids = createPidSequence(110); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class)); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java index 851f28a3dbf..c65205736dc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java @@ -9,7 +9,7 @@ import org.hl7.fhir.r4.model.Patient; import org.junit.*; import org.springframework.test.util.AopTestUtils; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.parser.StrictErrorHandler; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java index 626957b7a3d..f1fc6df2f0c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java @@ -1,7 +1,11 @@ package ca.uhn.fhir.jpa.search.reindex; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; @@ -24,14 +28,26 @@ import org.springframework.data.domain.SliceImpl; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; public class ResourceReindexingSvcImplTest extends BaseJpaTest { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java index e79e49fe76e..6ee336af59c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.config.TestDstu3Config; import ca.uhn.fhir.jpa.dao.BaseJpaTest; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.util.TestUtil; @@ -58,7 +59,7 @@ public class MatchUrlServiceTest extends BaseJpaTest { Location.SP_NEAR + "=1000.0:2000.0" + "&" + Location.SP_NEAR_DISTANCE + "=" + kmDistance + "|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); - map.setLocationDistance(); + Dstu3DistanceHelper.setNearDistance(Location.class, map); QuantityParam nearDistanceParam = map.getNearDistanceParam(); assertEquals(1, map.size()); @@ -75,7 +76,7 @@ public class MatchUrlServiceTest extends BaseJpaTest { "&" + Location.SP_NEAR_DISTANCE + "=2|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); - map.setLocationDistance(); + Dstu3DistanceHelper.setNearDistance(Location.class, map); fail(); } catch (IllegalArgumentException e) { @@ -92,7 +93,7 @@ public class MatchUrlServiceTest extends BaseJpaTest { "," + "2|http://unitsofmeasure.org|km", ourCtx.getResourceDefinition("Location")); - map.setLocationDistance(); + Dstu3DistanceHelper.setNearDistance(Location.class, map); fail(); } catch (IllegalArgumentException e) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestParserTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestParserTest.java index 493d493239e..14c443b5217 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestParserTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestParserTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.util.StopWatch; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +16,12 @@ public class StressTestParserTest extends BaseTest { private static final Logger ourLog = LoggerFactory.getLogger(StressTestParserTest.class); + /** + * On Xolo - 2020-03-14 - 150ms/pass after 199 passes + * @throws IOException + */ @Test + @Ignore public void test() throws IOException { FhirContext ctx = FhirContext.forR4(); String input = loadResource("/org/hl7/fhir/r4/model/valueset/valuesets.xml"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java index 6fd135eddc8..fd26081c3c8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java @@ -1,20 +1,16 @@ package ca.uhn.fhir.jpa.stresstest; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.TokenOrListParam; -import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.util.StopWatch; @@ -28,11 +24,10 @@ import org.apache.http.client.methods.HttpGet; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; -import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.junit.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; @@ -57,6 +52,7 @@ import static org.junit.Assert.fail; "max_db_connections=10" }) @DirtiesContext +@Ignore public class StressTestR4Test extends BaseResourceProviderR4Test { static { @@ -91,7 +87,7 @@ public class StressTestR4Test extends BaseResourceProviderR4Test { super.before(); myRequestValidatingInterceptor = new RequestValidatingInterceptor(); - FhirInstanceValidator module = new FhirInstanceValidator(); + FhirInstanceValidator module = new FhirInstanceValidator(myFhirCtx); module.setValidationSupport(myValidationSupport); myRequestValidatingInterceptor.addValidatorModule(module); @@ -591,8 +587,8 @@ public class StressTestR4Test extends BaseResourceProviderR4Test { get.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW); getResp = ourHttpClient.execute(get); try { - assertEquals(200, getResp.getStatusLine().getStatusCode()); String respBundleString = IOUtils.toString(getResp.getEntity().getContent(), Charsets.UTF_8); + assertEquals(respBundleString, 200, getResp.getStatusLine().getStatusCode()); respBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, respBundleString); myTaskCount++; } finally { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java index 9ad6d5a1578..fe28b223e84 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -98,13 +99,13 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test waitForActivatedSubscriptionCount(0); } - LinkedBlockingQueueSubscribableChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); + LinkedBlockingChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); if (processingChannel != null) { processingChannel.clearInterceptorsForUnitTest(); } myCountingInterceptor = new CountingInterceptor(); if (processingChannel != null) { - processingChannel.addInterceptorForUnitTest(myCountingInterceptor); + processingChannel.addInterceptor(myCountingInterceptor); } } @@ -193,8 +194,8 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test @Update public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { ourLog.info("Received Listener Update"); - ourUpdatedObservations.add(theObservation); ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); + ourUpdatedObservations.add(theObservation); extractHeaders(theRequest); return new MethodOutcome(new IdType("Observation/1"), false); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR5Test.java index add5808d885..f6c838ff9c1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR5Test.java @@ -1,10 +1,11 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r5.BaseResourceProviderR5Test; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -110,13 +111,13 @@ public abstract class BaseSubscriptionsR5Test extends BaseResourceProviderR5Test waitForActivatedSubscriptionCount(0); } - LinkedBlockingQueueSubscribableChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); + LinkedBlockingChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); if (processingChannel != null) { processingChannel.clearInterceptorsForUnitTest(); } myCountingInterceptor = new CountingInterceptor(); if (processingChannel != null) { - processingChannel.addInterceptorForUnitTest(myCountingInterceptor); + processingChannel.addInterceptor(myCountingInterceptor); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java deleted file mode 100644 index 6385b506216..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider; -import ca.uhn.fhir.rest.api.MethodOutcome; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Enumerations; -import org.hl7.fhir.r4.model.Observation; -import org.hl7.fhir.r4.model.SearchParameter; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import static org.junit.Assert.assertEquals; - - -public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test { - @Autowired - ISearchParamProvider origSearchParamProvider; - - @Before - public void useFhirClientSearchParamProvider() { - mySearchParamRegistry.setSearchParamProviderForUnitTest(new FhirClientSearchParamProvider(ourClient)); - } - - @After - public void revert() { - mySearchParamRegistry.setSearchParamProviderForUnitTest(origSearchParamProvider); - } - - @Test - public void testCustomSearchParam() throws Exception { - String criteria = "Observation?accessType=Catheter,PD%20Catheter"; - - SearchParameter sp = new SearchParameter(); - sp.addBase("Observation"); - sp.setCode("accessType"); - sp.setType(Enumerations.SearchParamType.TOKEN); - sp.setExpression("Observation.extension('Observation#accessType')"); - sp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - sp.setStatus(Enumerations.PublicationStatus.ACTIVE); - mySearchParameterDao.create(sp); - mySearchParamRegistry.forceRefresh(); - createSubscription(criteria, "application/json"); - waitForActivatedSubscriptionCount(1); - - { - Observation observation = new Observation(); - observation.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("Catheter")); - MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); - assertEquals(true, methodOutcome.getCreated()); - waitForQueueToDrain(); - waitForSize(1, ourUpdatedObservations); - } - { - Observation observation = new Observation(); - observation.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("PD Catheter")); - MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); - assertEquals(true, methodOutcome.getCreated()); - waitForQueueToDrain(); - waitForSize(2, ourUpdatedObservations); - } - { - Observation observation = new Observation(); - MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); - assertEquals(true, methodOutcome.getCreated()); - waitForQueueToDrain(); - waitForSize(2, ourUpdatedObservations); - } - { - Observation observation = new Observation(); - observation.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("XXX")); - MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); - assertEquals(true, methodOutcome.getCreated()); - waitForQueueToDrain(); - waitForSize(2, ourUpdatedObservations); - } - } -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java deleted file mode 100644 index 5d23b900dc1..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; -import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider; -import ca.uhn.fhir.rest.api.Constants; -import org.hl7.fhir.dstu3.model.Subscription; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class FhirClientSubscriptionProviderTest extends BaseSubscriptionsR4Test { - @Autowired - SubscriptionLoader mySubscriptionLoader; - @Autowired - ISubscriptionProvider origSubscriptionProvider; - @Autowired - AutowireCapableBeanFactory autowireCapableBeanFactory; - - @Before - public void useFhirClientSubscriptionProvider() { - FhirClientSubscriptionProvider subscriptionProvider = new FhirClientSubscriptionProvider(ourClient); - // This bean is only available in the standalone subscription context, so we have to autowire it manually. - autowireCapableBeanFactory.autowireBean(subscriptionProvider); - mySubscriptionLoader.setSubscriptionProviderForUnitTest(subscriptionProvider); - } - - @After - public void revert() { - mySubscriptionLoader.setSubscriptionProviderForUnitTest(origSubscriptionProvider); - } - - private String myCode = "1000000050"; - - @Test - public void testSubscriptionLoaderFhirClient() throws Exception { - String payload = "application/fhir+json"; - - String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; - String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; - - - List subs = new ArrayList<>(); - createSubscription(criteria1, payload); - createSubscription(criteria2, payload); - waitForActivatedSubscriptionCount(2); - - sendObservation(myCode, "SNOMED-CT"); - - waitForSize(0, ourCreatedObservations); - waitForSize(1, ourUpdatedObservations); - assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); - } -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java index bb3d30a6ce7..a9818ecac0d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java @@ -1,13 +1,15 @@ package ca.uhn.fhir.jpa.subscription; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelWithHandlers; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionSubmitInterceptorLoader; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelWithHandlers; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.SubscriptionDeliveringEmailSubscriber; import org.hl7.fhir.dstu2.model.Subscription; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; @@ -20,7 +22,7 @@ public class SubscriptionTestUtil { @Autowired private DaoConfig myDaoConfig; @Autowired - private SubscriptionInterceptorLoader mySubscriptionInterceptorLoader; + private SubscriptionSubmitInterceptorLoader mySubscriptionSubmitInterceptorLoader; @Autowired private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; @Autowired @@ -29,7 +31,7 @@ public class SubscriptionTestUtil { private SubscriptionChannelRegistry mySubscriptionChannelRegistry; public int getExecutorQueueSize() { - LinkedBlockingQueueSubscribableChannel channel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); + LinkedBlockingChannel channel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); return channel.getQueueSizeForUnitTest(); } @@ -48,22 +50,22 @@ public class SubscriptionTestUtil { public void registerEmailInterceptor() { myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); - mySubscriptionInterceptorLoader.registerInterceptors(); + mySubscriptionSubmitInterceptorLoader.start(); } public void registerRestHookInterceptor() { myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); - mySubscriptionInterceptorLoader.registerInterceptors(); + mySubscriptionSubmitInterceptorLoader.start(); } public void registerWebSocketInterceptor() { myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); - mySubscriptionInterceptorLoader.registerInterceptors(); + mySubscriptionSubmitInterceptorLoader.start(); } public void unregisterSubscriptionInterceptor() { myDaoConfig.clearSupportedSubscriptionTypesForUnitTest(); - mySubscriptionInterceptorLoader.unregisterInterceptorsForUnitTest(); + mySubscriptionSubmitInterceptorLoader.unregisterInterceptorsForUnitTest(); } public int getExecutorQueueSizeForUnitTests() { @@ -79,7 +81,7 @@ public class SubscriptionTestUtil { public void setEmailSender(IIdType theIdElement) { ActiveSubscription activeSubscription = mySubscriptionRegistry.get(theIdElement.getIdPart()); - SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.get(activeSubscription.getChannelName()); + SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.getDeliveryReceiverChannel(activeSubscription.getChannelName()); SubscriptionDeliveringEmailSubscriber subscriber = (SubscriptionDeliveringEmailSubscriber) subscriptionChannelWithHandlers.getDeliveryHandlerForUnitTest(); subscriber.setEmailSender(myEmailSender); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java similarity index 90% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptorTest.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java index f266ecddd21..b15e699ce7d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptorTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionValidatingInterceptor; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.r4.model.Subscription; import org.junit.Before; @@ -19,21 +20,22 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class SubscriptionActivatingInterceptorTest { +public class SubscriptionValidatingInterceptorTest { @Mock public DaoRegistry myDaoRegistry; - private SubscriptionActivatingInterceptor mySvc; + private SubscriptionValidatingInterceptor mySvc; private FhirContext myCtx = FhirContext.forR4(); @Mock private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; @Before public void before() { - mySvc = new SubscriptionActivatingInterceptor(); + mySvc = new SubscriptionValidatingInterceptor(); mySvc.setSubscriptionCanonicalizerForUnitTest(new SubscriptionCanonicalizer(myCtx)); mySvc.setDaoRegistryForUnitTest(myDaoRegistry); mySvc.setSubscriptionStrategyEvaluatorForUnitTest(mySubscriptionStrategyEvaluator); + mySvc.setFhirContextForUnitTest(myCtx); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java index 82b03224c93..9af8a8152a7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.subscription.email; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java index e80450b1ca8..e1b02a52986 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.subscription.email; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.EmailDetails; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.EmailDetails; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.ServerSetup; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java index bb98b5de8d9..b4155968795 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java @@ -7,8 +7,11 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; import ca.uhn.fhir.jpa.util.CoordCalculatorTest; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.rest.param.*; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java index cb2cc585685..9753032bc79 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java @@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -45,11 +44,6 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc @Autowired private SubscriptionTestUtil mySubscriptionTestUtil; - @After - public void afterResetSubscriptionActivatingInterceptor() { - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); - } - @After public void afterUnregisterRestHookListener() { mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); @@ -57,7 +51,6 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc @Before public void beforeSetSubscriptionActivatingInterceptor() { - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java index cf9591cf8cb..bb228a84bbf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java @@ -1,7 +1,7 @@ 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.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java index 28f36960a5e..e1ae86cdb59 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java @@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorService; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; 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.interceptor.SubscriptionDebugLogInterceptor; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java index 7682c780025..2f5e5fc32c2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java @@ -21,9 +21,16 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Test the rest-hook subscriptions @@ -229,6 +236,34 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { waitForSize(100, ourUpdatedObservations); } + + @Test + public void testSubscriptionRegistryLoadsSubscriptionsFromDatabase() throws Exception { + String payload = "application/fhir+json"; + + String code = "1000000050"; + String criteria1 = "Observation?"; + + createSubscription(criteria1, payload); + waitForActivatedSubscriptionCount(1); + + // Manually unregister all subscriptions + mySubscriptionRegistry.unregisterAllSubscriptions(); + waitForActivatedSubscriptionCount(0); + + // Force a reload + mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); + + // Send a matching observation + Observation observation = new Observation(); + observation.getIdentifierFirstRep().setSystem("foo").setValue("ID"); + observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT"); + observation.setStatus(Observation.ObservationStatus.FINAL); + myObservationDao.create(observation); + + waitForSize(1, ourUpdatedObservations); + } + @Test public void testActiveSubscriptionShouldntReActivate() throws Exception { String criteria = "Observation?code=111111111&_format=xml"; @@ -869,7 +904,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { String criteriaGood = "Patient?gender=male"; Subscription subscription = newSubscription(criteriaGood, payload); ourClient.create().resource(subscription).execute(); - assertEquals(1, subscriptionCount()); + await().until(() -> subscriptionCount() == 1); } /** @@ -889,7 +924,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { assertEquals(Subscription.SubscriptionStatus.REQUESTED, subscription.getStatus()); } finally { - existingSupportedSubscriptionTypes.forEach(t-> myDaoConfig.addSupportedSubscriptionType(t)); + existingSupportedSubscriptionTypes.forEach(t -> myDaoConfig.addSupportedSubscriptionType(t)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR5Test.java index 1aa5a058ddb..8e6d2ac20d7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR5Test.java @@ -594,6 +594,7 @@ public class RestHookTestR5Test extends BaseSubscriptionsR5Test { // Should see 1 subscription notification waitForSize(0, ourCreatedObservations); waitForSize(1, ourUpdatedObservations); + waitForSize(1, ourContentTypes); assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0)); Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java index f8b9dca35a8..67321530b34 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java @@ -1,10 +1,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.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Observation; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java index ae19d59c512..04110bd6db6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java @@ -2,9 +2,8 @@ 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.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.Create; @@ -18,9 +17,18 @@ import com.google.common.collect.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.Collections; @@ -56,7 +64,6 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); } @Before @@ -68,7 +75,6 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B public void beforeReset() { ourCreatedObservations.clear(); ourUpdatedObservations.clear(); - SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); } private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java index 82bbc11eeec..dbd1c5272ac 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java @@ -2,7 +2,7 @@ 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.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java index 1390748b911..356705190f8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.subscription.resthook; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.subscription.FhirR4Util; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java index 73474070793..e1730036a88 100755 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java @@ -7,10 +7,10 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.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.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; import org.apache.commons.lang3.Validate; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java index 8ce8bb95fc7..72ea14e1e99 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java @@ -1,13 +1,14 @@ 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.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; +import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; -import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl; +import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; +import ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -17,6 +18,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.test.utilities.ProxyUtil; import com.google.common.collect.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -43,7 +45,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; @@ -72,7 +73,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te @Autowired private SubscriptionTestUtil mySubscriptionTestUtil; @Autowired - private SubscriptionTriggeringSvcImpl mySubscriptionTriggeringSvc; + private ISubscriptionTriggeringSvc mySubscriptionTriggeringSvc; @Autowired private ISchedulerService mySchedulerService; @@ -94,8 +95,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); - mySubscriptionTriggeringSvc.cancelAll(); - mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null); + SubscriptionTriggeringSvcImpl svc = ProxyUtil.getSingletonTarget(mySubscriptionTriggeringSvc, SubscriptionTriggeringSvcImpl.class); + svc.cancelAll(); + svc.setMaxSubmitPerPass(null); myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); } @@ -182,7 +184,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(subscriptionId) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue())) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue())) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); @@ -225,14 +227,15 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te waitForSize(50, ourUpdatedPatients); beforeReset(); - mySubscriptionTriggeringSvc.setMaxSubmitPerPass(33); + SubscriptionTriggeringSvcImpl svc = ProxyUtil.getSingletonTarget(mySubscriptionTriggeringSvc, SubscriptionTriggeringSvcImpl.class); + svc.setMaxSubmitPerPass(33); Parameters response = ourClient .operation() .onInstance(sub1id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Observation?")) - .andParameter(SubscriptionTriggeringProvider.RESOURCE_ID, new UriType("Observation/O2")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Observation?")) + .andParameter(ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID, new UriType("Observation/O2")) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -241,7 +244,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(sub2id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?")) .execute(); responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -280,9 +283,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(sub2id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?_id=P0")) - .andParameter(SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?_id=P1")) - .andParameter(SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?_id=P2")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?_id=P0")) + .andParameter(ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?_id=P1")) + .andParameter(ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?_id=P2")) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -295,7 +298,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te mySubscriptionTriggeringSvc.runDeliveryPass(); mySubscriptionTriggeringSvc.runDeliveryPass(); mySubscriptionTriggeringSvc.runDeliveryPass(); - assertEquals(0, mySubscriptionTriggeringSvc.getActiveJobCount()); + + SubscriptionTriggeringSvcImpl svc = ProxyUtil.getSingletonTarget(mySubscriptionTriggeringSvc, SubscriptionTriggeringSvcImpl.class); + assertEquals(0, svc.getActiveJobCount()); assertEquals(0, ourCreatedPatients.size()); await().until(() -> ourUpdatedPatients.size() == 3); @@ -324,7 +329,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(sub2id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?_id=P0,P1,P2")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?_id=P0,P1,P2")) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -362,13 +367,14 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te waitForSize(50, ourUpdatedPatients); beforeReset(); - mySubscriptionTriggeringSvc.setMaxSubmitPerPass(33); + SubscriptionTriggeringSvcImpl svc = ProxyUtil.getSingletonTarget(mySubscriptionTriggeringSvc, SubscriptionTriggeringSvcImpl.class); + svc.setMaxSubmitPerPass(33); Parameters response = ourClient .operation() .onInstance(sub1id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Observation?_count=10")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Observation?_count=10")) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -377,7 +383,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(sub2id) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Patient?_count=16")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Patient?_count=16")) .execute(); responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -400,7 +406,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onType(Subscription.class) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Observation")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Observation")) .execute(); fail(); } catch (InvalidRequestException e) { @@ -428,13 +434,14 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te waitForSize(0, ourUpdatedPatients); beforeReset(); - mySubscriptionTriggeringSvc.setMaxSubmitPerPass(50); + SubscriptionTriggeringSvcImpl svc = ProxyUtil.getSingletonTarget(mySubscriptionTriggeringSvc, SubscriptionTriggeringSvcImpl.class); + svc.setMaxSubmitPerPass(50); Parameters response = ourClient .operation() .onType(Subscription.class) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.SEARCH_URL, new StringType("Observation?")) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL, new StringType("Observation?")) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); assertThat(responseValue, containsString("Subscription triggering job submitted as JOB ID")); @@ -471,7 +478,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te .operation() .onInstance(subscriptionId) .named(JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) - .withParameter(Parameters.class, SubscriptionTriggeringProvider.RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue())) + .withParameter(Parameters.class, ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue())) .execute(); String responseValue = response.getParameter().get(0).getValue().primitiveValue(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java index 57e360909b4..dbc14355c57 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java @@ -9,7 +9,12 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Subscription; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -20,6 +25,7 @@ import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; @@ -112,10 +118,12 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes Session session = connection.get(2, TimeUnit.SECONDS); ourLog.info("Connected to WS: {}", session.isOpen()); + + await().until(() -> mySubscriptionRegistry.size() == 1); } @Test - public void createObservation() throws Exception { + public void createObservation() { Observation observation = new Observation(); CodeableConcept codeableConcept = new CodeableConcept(); observation.setCode(codeableConcept); @@ -139,7 +147,7 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes } @Test - public void createObservationThatDoesNotMatch() throws Exception { + public void createObservationThatDoesNotMatch() { Observation observation = new Observation(); CodeableConcept codeableConcept = new CodeableConcept(); observation.setCode(codeableConcept); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java index 3fc27cdf1f4..0d33502ca51 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -13,7 +13,6 @@ import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.junit.After; import org.junit.Before; -import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImplTest.java index 96cfa27e116..1b61e70bb28 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImplTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java index cb5a2b9d82b..db5a88b5698 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.util.TestUtil; @@ -158,7 +158,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); + IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); @@ -188,7 +188,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("17788-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); + IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("17788-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); @@ -206,7 +206,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); + IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); List> properties = Lists.newArrayList(new CodeType("SCALE_TYP")); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, properties); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java index 0d0dd62fe54..c0430daf501 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java @@ -1,10 +1,12 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; @@ -16,25 +18,36 @@ import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.ValueSet; +import org.junit.After; import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import static org.apache.commons.lang3.StringUtils.countMatches; import static org.apache.commons.lang3.StringUtils.leftPad; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.matchesPattern; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcDeltaR4Test.class); + @After + public void after() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); + } + @Test public void testAddRootConcepts() { @@ -115,17 +128,23 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { ourLog.info("All concepts: {}", myTermConceptDao.findAll()); }); + myCaptureQueriesListener.clear(); + delta = new CustomTerminologySet(); TermConcept root = delta.addRootConcept("RootA", "Root A"); root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA"); root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAB").setDisplay("Child AB"); myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + assertHierarchyContains( "RootA seq=0", " ChildAA seq=0", " ChildAB seq=1", "RootB seq=0" ); + } @Test @@ -164,6 +183,12 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { ); assertEquals(2, outcome.getUpdatedConceptCount()); + runInTransaction(() -> { + TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA").orElseThrow(() -> new IllegalStateException()); + assertEquals(2, concept.getParents().size()); + assertThat(concept.getParentPidsAsString(), matchesPattern("^[0-9]+ [0-9]+$")); + }); + } @Test @@ -344,11 +369,54 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { assertEquals("http://foo", outcome.getUrl()); assertEquals(CodeSystem.CodeSystemContentMode.NOTPRESENT, outcome.getContent()); - IContextValidationSupport.LookupCodeResult lookup = myTermSvc.lookupCode(myFhirCtx, "http://foo", "CBC"); + IValidationSupport.LookupCodeResult lookup = myTermSvc.lookupCode(myValidationSupport, "http://foo", "CBC"); assertEquals("Complete Blood Count", lookup.getCodeDisplay()); } + @Test + public void testAddLargeHierarchy() { + myDaoConfig.setDeferIndexingForCodesystemsOfSize(5); + + createNotPresentCodeSystem(); + ValueSet vs; + vs = expandNotPresentCodeSystem(); + assertEquals(0, vs.getExpansion().getContains().size()); + + CustomTerminologySet delta = new CustomTerminologySet(); + + // Create a nice deep hierarchy + TermConcept concept = delta.addRootConcept("Root", "Root"); + int nestedDepth = 10; + for (int i = 0; i < nestedDepth; i++) { + String name = concept.getCode(); + concept = concept.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode(name + "0").setDisplay(name + "0"); + } + + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + + assertFalse(myTermDeferredStorageSvc.isStorageQueueEmpty()); + while (!myTermDeferredStorageSvc.isStorageQueueEmpty()) { + myTermDeferredStorageSvc.saveDeferred(); + } + + List expectedHierarchy = new ArrayList<>(); + for (int i = 0; i < nestedDepth + 1; i++) { + String expected = leftPad("", i, " ") + + "Root" + + leftPad("", i, "0") + + " seq=0"; + expectedHierarchy.add(expected); + } + + assertHierarchyContains(expectedHierarchy.toArray(new String[0])); + + } + + @Autowired + private ITermDeferredStorageSvc myTermDeferredStorageSvc; + + @Test public void testAddModifiesExistingCodesInPlace() { @@ -359,7 +427,7 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA0", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + assertEquals("CODEA0", myTermSvc.lookupCode(myValidationSupport, "http://foo", "codea").getCodeDisplay()); // Add codes again with different display delta = new CustomTerminologySet(); @@ -367,12 +435,12 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { delta.addRootConcept("codeb", "CODEB1"); outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA1", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + assertEquals("CODEA1", myTermSvc.lookupCode(myValidationSupport, "http://foo", "codea").getCodeDisplay()); // Add codes again with no changes outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA1", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + assertEquals("CODEA1", myTermSvc.lookupCode(myValidationSupport, "http://foo", "codea").getCodeDisplay()); } @Test @@ -409,7 +477,7 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { .setCode("useless_sct_code") .setValue(new Coding("http://snomed.info", "1234567", "Choked on large meal (finding)")); - IContextValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(myFhirCtx, "http://foo/cs", "lunch"); + IValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(myValidationSupport, "http://foo/cs", "lunch"); assertEquals(true, result.isFound()); assertEquals("lunch", result.getSearchedForCode()); assertEquals("http://foo/cs", result.getSearchedForSystem()); @@ -484,9 +552,15 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAAA").isPresent())); // Remove CodeA - delta = new CustomTerminologySet(); - delta.addRootConcept("codeA"); - myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta); + myCaptureQueriesListener.clear(); + runInTransaction(()->{ + CustomTerminologySet delta2 = new CustomTerminologySet(); + delta2.addRootConcept("codeA"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta2); + }); + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + + ourLog.info("*** Done removing"); assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeB").isPresent())); assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeA").isPresent())); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index 3f2e784734a..d84e536e974 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; @@ -12,14 +14,18 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.VersionIndependentConcept; import com.google.common.collect.Lists; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.ValueSet; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +40,14 @@ import java.util.Set; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplDstu3Test.class); @@ -100,6 +112,8 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTerminologyDeferredStorageSvc.saveAllDeferred(); + return id; } @@ -245,7 +259,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(CS_URL); include.addConcept().setCode("childAAB"); - ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + ValueSet outcome = myTermSvc.expandValueSet(null, vs); List codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("childAAB")); @@ -279,7 +293,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("propA") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("valueAAA"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("childAAA")); @@ -292,7 +306,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("propB") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("foo"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); @@ -305,7 +319,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("propA") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("valueAAA"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, empty()); } @@ -332,7 +346,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("3rdParty"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4")); @@ -349,7 +363,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("3rdparty"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4")); } @@ -376,7 +390,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("LOINC"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("47239-9")); @@ -393,7 +407,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("loinc"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("47239-9")); } @@ -416,7 +430,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("3rdParty"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("47239-9")); @@ -429,7 +443,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("3rdparty"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("47239-9")); } @@ -452,7 +466,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("LOINC"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4")); @@ -465,7 +479,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("copyright") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("loinc"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4")); } @@ -489,7 +503,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle op=ISA on property copyright"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -512,7 +526,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Invalid filter, property copyright is LOINC-specific and cannot be used with system: http://example.com/my_code_system"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -534,7 +548,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle value=bogus on property copyright"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -559,7 +573,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -576,7 +590,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); @@ -593,7 +607,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); @@ -610,7 +624,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); } @@ -637,7 +651,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); } @@ -660,7 +674,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); @@ -673,7 +687,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); @@ -686,7 +700,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); // Include @@ -698,7 +712,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); } @@ -720,7 +734,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("ancestor") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); } @@ -744,7 +758,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle op=ISA on property ancestor"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -767,7 +781,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Invalid filter, property ancestor is LOINC-specific and cannot be used with system: http://example.com/my_code_system"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -792,7 +806,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); @@ -809,7 +823,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); @@ -826,7 +840,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9")); @@ -843,7 +857,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9")); } @@ -870,7 +884,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); } @@ -893,7 +907,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); // Include @@ -905,7 +919,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -918,7 +932,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3")); @@ -931,7 +945,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3")); } @@ -954,7 +968,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("child") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); } @@ -978,7 +992,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle op=ISA on property child"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1001,7 +1015,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Invalid filter, property child is LOINC-specific and cannot be used with system: http://example.com/my_code_system"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1026,7 +1040,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); @@ -1043,7 +1057,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); @@ -1060,7 +1074,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); @@ -1077,7 +1091,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); } @@ -1104,7 +1118,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); } @@ -1127,7 +1141,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); // Include @@ -1139,7 +1153,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -1152,7 +1166,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); @@ -1165,7 +1179,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); } @@ -1188,7 +1202,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("descendant") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); } @@ -1212,7 +1226,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle op=ISA on property descendant"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1235,7 +1249,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Invalid filter, property descendant is LOINC-specific and cannot be used with system: http://example.com/my_code_system"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1260,7 +1274,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9")); @@ -1277,7 +1291,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3")); @@ -1294,7 +1308,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); @@ -1311,7 +1325,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9")); } @@ -1338,7 +1352,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); } @@ -1361,7 +1375,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("50015-7"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3")); @@ -1374,7 +1388,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-3"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-4", "47239-9")); @@ -1387,7 +1401,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("43343-4"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); // Include @@ -1399,7 +1413,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.EQUAL) .setValue("47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); assertEquals(0, outcome.getExpansion().getContains().size()); } @@ -1421,7 +1435,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("parent") .setOp(ValueSet.FilterOperator.IN) .setValue("50015-7,43343-3,43343-4,47239-9"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); } @@ -1445,7 +1459,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Don't know how to handle op=ISA on property parent"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1468,7 +1482,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Invalid filter, property parent is LOINC-specific and cannot be used with system: http://example.com/my_code_system"); - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); } @Test @@ -1493,7 +1507,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue(".*\\^Donor$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9")); } @@ -1520,7 +1534,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("HELLO") .setOp(ValueSet.FilterOperator.REGEX) .setValue("12345-1|12345-2"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7", "47239-9")); } @@ -1543,7 +1557,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue(".*\\^Donor$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -1556,7 +1570,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue("\\^Donor$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -1569,7 +1583,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue("\\^Dono$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, empty()); @@ -1582,7 +1596,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue("^Donor$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, empty()); @@ -1595,7 +1609,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue("\\^Dono"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("50015-7")); @@ -1608,7 +1622,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { .setProperty("SYSTEM") .setOp(ValueSet.FilterOperator.REGEX) .setValue("^Ser$"); - outcome = myTermSvc.expandValueSetInMemory(vs, null); + outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("43343-3", "43343-4")); @@ -1623,7 +1637,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { ValueSet vs = new ValueSet(); ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(CS_URL); - ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + ValueSet outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("ParentWithNoChildrenA", "ParentWithNoChildrenB", "ParentWithNoChildrenC", "ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB")); @@ -1740,7 +1754,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(CS_URL); include.addConcept().setCode("childAAB"); - ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + ValueSet outcome = myTermSvc.expandValueSet(null, vs); codes = toCodesContains(outcome.getExpansion().getContains()); assertThat(codes, containsInAnyOrder("childAAB")); @@ -1935,7 +1949,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { @Ignore public void testValidateCodeWithProperties() { createCodeSystem(); - IValidationSupport.CodeValidationResult code = myValidationSupport.validateCode(myFhirCtx, CS_URL, "childAAB", null, (String)null); + IValidationSupport.CodeValidationResult code = myValidationSupport.validateCode(myValidationSupport, new ConceptValidationOptions(), CS_URL, "childAAB", null, null); assertEquals(true, code.isOk()); assertEquals(2, code.getProperties().size()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index f063d4b22df..ad35e52be0f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -1,13 +1,14 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.entity.TermConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.entity.TermValueSet; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; @@ -21,6 +22,7 @@ import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.codesystems.HttpVerb; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.junit.AfterClass; import org.junit.Rule; import org.junit.Test; @@ -186,7 +188,7 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); TermValueSet termValueSet = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable).get(); @@ -223,7 +225,7 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); TermValueSet termValueSet = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable).get(); @@ -1774,10 +1776,10 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { public void testValidateCode() { createCodeSystem(); - IContextValidationSupport.CodeValidationResult validation = myTermSvc.validateCode(myFhirCtx, CS_URL, "ParentWithNoChildrenA", null, (String)null); + IValidationSupport.CodeValidationResult validation = myTermSvc.validateCode(myValidationSupport, new ConceptValidationOptions(), CS_URL, "ParentWithNoChildrenA", null, null); assertEquals(true, validation.isOk()); - validation = myTermSvc.validateCode(myFhirCtx, CS_URL, "ZZZZZZZ", null, (String)null); + validation = myTermSvc.validateCode(myValidationSupport, new ConceptValidationOptions(), CS_URL, "ZZZZZZZ", null, null); assertEquals(false, validation.isOk()); } @@ -1795,32 +1797,32 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, null, null); + IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, "BOGUS", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, "11378-7", "Systolic blood pressure at First encounter", null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, "http://acme.org", "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, coding, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -1828,12 +1830,15 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(coding); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, null, codeableConcept); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } + ValidationOptions optsNoGuess = new ValidationOptions(); + ValidationOptions optsGuess = new ValidationOptions().guessSystem(); + @Test public void testValidateCodeIsInPreExpandedValueSetWithClientAssignedId() throws Exception { myDaoConfig.setPreExpandValueSets(true); @@ -1848,32 +1853,33 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, null, null); + IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, "BOGUS", null, null, null); + + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); assertNull(result); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, "11378-7", "Systolic blood pressure at First encounter", null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, "http://acme.org", "11378-7", null, null, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, coding, null); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -1881,7 +1887,7 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { CodeableConcept codeableConcept = new CodeableConcept(); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(coding); - result = myTermSvc.validateCodeIsInPreExpandedValueSet(valueSet, null, null, null, null, codeableConcept); + result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); assertTrue(result.isResult()); assertEquals("Validation succeeded", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java index f727931f104..ce3bdb87945 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -69,7 +70,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myCaptureQueriesListener.clear(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); assertEquals(24, expandedValueSet.getExpansion().getContains().size()); runInTransaction(()->{ @@ -82,7 +83,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { assertEquals(0, myTermValueSetConceptDao.count()); }); - expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + expandedValueSet = myTermSvc.expandValueSet(null, valueSet); assertEquals(24, expandedValueSet.getExpansion().getContains().size()); } @@ -103,7 +104,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myCaptureQueriesListener.clear(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); @@ -185,7 +186,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myCaptureQueriesListener.clear(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, 0, 100); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(3, expandedValueSet.getExpansion().getContains().size()); @@ -204,7 +205,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -260,7 +261,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { assertEquals("Systolic blood pressure 8 hour minimum", containsComponent.getDisplay()); assertFalse(containsComponent.hasDesignation()); - expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -331,7 +332,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -406,7 +407,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), 23); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(0) + .setCount(23); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -475,7 +479,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), 23); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(0) + .setCount(23); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -544,7 +551,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), 0); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(0) + .setCount(0); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); String expanded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet); ourLog.info("Expanded ValueSet:\n" + expanded); @@ -573,7 +583,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, myDaoConfig.getPreExpandValueSetsDefaultOffset(), 0); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(0) + .setCount(0); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -601,7 +614,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, 1, myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(1) + .setCount(1000); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -662,7 +678,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, 1, myDaoConfig.getPreExpandValueSetsDefaultCount()); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(1) + .setCount(1000); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -723,7 +742,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, 1, 22); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(1) + .setCount(22); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -769,7 +791,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { ValueSet vs = new ValueSet(); ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem("http://unknown-system"); - ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + ValueSet outcome = myTermSvc.expandValueSet(new ValueSetExpansionOptions().setFailOnMissingCodeSystem(false), vs); assertEquals(0, outcome.getExpansion().getContains().size()); String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); ourLog.info(encoded); @@ -793,7 +815,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - ValueSet expandedValueSet = myTermSvc.expandValueSet(valueSet, 1, 22); + ValueSetExpansionOptions options = new ValueSetExpansionOptions() + .setOffset(1) + .setCount(22); + ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal()); @@ -852,7 +877,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(CS_URL); try { - myTermSvc.expandValueSetInMemory(vs, null); + myTermSvc.expandValueSet(null, vs); fail(); } catch (InternalErrorException e) { assertEquals("Expansion of ValueSet produced too many codes (maximum 50) - Operation aborted!", e.getMessage()); @@ -863,7 +888,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { vs = new ValueSet(); include = vs.getCompose().addInclude(); include.setSystem(CS_URL); - ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null); + ValueSet outcome = myTermSvc.expandValueSet(null, vs); assertEquals(109, outcome.getExpansion().getContains().size()); } @@ -878,7 +903,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test { ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(CS_URL); - myTermSvc.expandValueSet(vs, myValueSetCodeAccumulator); + myTermSvc.expandValueSet(null, vs, myValueSetCodeAccumulator); verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/ValidatorAcrossVersionsTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/ValidatorAcrossVersionsTest.java index 87fcc302bba..3a55268c859 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/ValidatorAcrossVersionsTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/ValidatorAcrossVersionsTest.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.validator; import static org.junit.Assert.*; -import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.junit.AfterClass; import org.junit.Test; @@ -45,7 +45,7 @@ public class ValidatorAcrossVersionsTest { FhirValidator val = ctxDstu2.newValidator(); val.setValidateAgainstStandardSchema(false); val.setValidateAgainstStandardSchematron(false); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ctxDstu2)); QuestionnaireResponse resp = new QuestionnaireResponse(); resp.setAuthored(DateTimeDt.withCurrentTime()); diff --git a/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/CodeSystem-iar-citizenship-status.xml b/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/CodeSystem-iar-citizenship-status.xml new file mode 100644 index 00000000000..db87c99a0d9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/CodeSystem-iar-citizenship-status.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/ValueSet-iar-citizenship-status.xml b/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/ValueSet-iar-citizenship-status.xml new file mode 100644 index 00000000000..2c75714b646 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/r4/iar/ValueSet-iar-citizenship-status.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-example/.gitignore b/hapi-fhir-jpaserver-example/.gitignore deleted file mode 100644 index e52bde55f83..00000000000 --- a/hapi-fhir-jpaserver-example/.gitignore +++ /dev/null @@ -1,128 +0,0 @@ -/target -/jpaserver_derby_files -*.log -ca.uhn.fhir.jpa.entity.ResourceTable/ - -# Created by https://www.gitignore.io - -### Java ### -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties - - -### Vim ### -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -*.un~ -Session.vim -.netrwhist -*~ - - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties - - - -### Eclipse ### -*.pydevproject -.metadata -.gradle -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.loadpath - -# Eclipse Core -.project - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# JDT-specific (Eclipse Java Development Tools) - -# PDT-specific -.buildpath - -# sbteclipse plugin -.target - -# TeXlipse plugin -.texlipse - diff --git a/hapi-fhir-jpaserver-example/Dockerfile b/hapi-fhir-jpaserver-example/Dockerfile deleted file mode 100644 index 1c26aefb5ad..00000000000 --- a/hapi-fhir-jpaserver-example/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM jetty:9-jre8-alpine -USER jetty:jetty -ADD ./target/hapi-fhir-jpaserver-example.war /var/lib/jetty/webapps/root.war -EXPOSE 8080 diff --git a/hapi-fhir-jpaserver-example/README.md b/hapi-fhir-jpaserver-example/README.md deleted file mode 100644 index 5c37386c841..00000000000 --- a/hapi-fhir-jpaserver-example/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Unsupported - -This [hapi-fhir-jpaserver-example](https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jpaserver-example) project is no longer supported. - - -## Supported JPA Example - -The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) -project within the [hapifhir](https://github.com/hapifhir) GitHub Organization. - -## Previous Documentation - -Below is the original documentation for this project. Note that this documentation is no longer being updated. - -#### Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ - -Install Tomcat. - -Make sure you have Tomcat set up in IntelliJ. - -- File->Settings->Build, Execution, Deployment->Application Servers -- Click + -- Select "Tomcat Server" -- Enter the path to your tomcat deployment for both Tomcat Home (IntelliJ will fill in base directory for you) - -Add a Run Configuration for running hapi-fhir-jpaserver-example under Tomcat - -- Run->Edit Configurations -- Click the green + -- Select Tomcat Server, Local -- Change the name to whatever you wish -- Uncheck the "After launch" checkbox -- On the "Deployment" tab, click the green + -- Select "Artifact" -- Select "hapi-fhir-jpaserver-example:war" -- In "Application context" type /hapi - -Run the configuration. - -- You should now have an "Application Servers" in the list of windows at the bottom. -- Click it. -- Select your server, and click the green triangle (or the bug if you want to debug) -- Wait for the console output to stop - -Point your browser (or fiddler, or what have you) to `http://localhost:8080/hapi/baseDstu3/Patient` - -You should get an empty bundle back. - - -#### Running hapi-fhir-jpaserver-example in a Docker container - -Execute the `build-docker-image.sh` script to build the docker image. - -Use this command to start the container: - `docker run -d --name hapi-fhir-jpaserver-example -p 8080:8080 hapi-fhir/hapi-fhir-jpaserver-example` - -Note: with this command data is persisted across container restarts, but not after removal of the container. Use a docker volume mapping on /var/lib/jetty/target to achieve this. - -After the docker container initial startup, point your browser (or fiddler, or what have you) to `http://localhost:8080/baseDstu3/Patient` - -You should get an empty bundle back. -#### Using ElasticSearch as the search engine instead of the default Apache Lucene -1. Install ElasticSearch server and the phonetic plugin - * Download ElasticSearch from https://www.elastic.co/downloads/elasticsearch - * ```cd {your elasticsearch directory}``` - * ```bin/plugin install analysis-phonetic``` - * start ElasticSearch server: ```./bin/elasticsearch``` -2. Replace configuration in web.xml - * replace the configuration class ```ca.uhn.fhir.jpa.demo.FhirServerConfig``` in web.xml by ```ca.uhn.fhir.jpa.demo.elasticsearch.FhirServerConfig``` -3. Start server by runing: ```mvn jetty:run``` -4. Limitations: - * Hibernate search are not compatible with all ElasticSearch version. If you are using Hibernate search: 5.6 or 5.7, the compatible ElasticSearch version is 2.0 - 2.4. If you are using Hibernate search: 5.8 or 5.9, the compatible ElasticSearch version is - 2.0 - 5.6. - * Please check all the limitations in the reference documentation: https://docs.jboss.org/hibernate/search/5.7/reference/en-US/html_single/#elasticsearch-limitations before use the integration. diff --git a/hapi-fhir-jpaserver-example/build-docker-image.sh b/hapi-fhir-jpaserver-example/build-docker-image.sh deleted file mode 100755 index 6d1cfc3ca10..00000000000 --- a/hapi-fhir-jpaserver-example/build-docker-image.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -mvn package && \ - docker build -t hapi-fhir/hapi-fhir-jpaserver-example . - diff --git a/hapi-fhir-jpaserver-example/pom.xml b/hapi-fhir-jpaserver-example/pom.xml deleted file mode 100644 index c47d1a1f21b..00000000000 --- a/hapi-fhir-jpaserver-example/pom.xml +++ /dev/null @@ -1,336 +0,0 @@ - - 4.0.0 - - - - ca.uhn.hapi.fhir - hapi-fhir - 4.0.0-SNAPSHOT - ../pom.xml - - - hapi-fhir-jpaserver-example - war - - HAPI FHIR JPA Server - Example - - - - oss-snapshots - - true - - https://oss.sonatype.org/content/repositories/snapshots/ - - - - - - org.eclipse.jetty.websocket - websocket-api - ${jetty_version} - - - org.eclipse.jetty.websocket - websocket-client - ${jetty_version} - - - mysql - mysql-connector-java - 6.0.5 - - - - - - ca.uhn.hapi.fhir - hapi-fhir-base - ${project.version} - - - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu3 - ${project.version} - - - - - ca.uhn.hapi.fhir - hapi-fhir-jpaserver-base - ${project.version} - - - - ca.uhn.hapi.fhir - hapi-fhir-jpaserver-elasticsearch - ${project.version} - - - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - ${project.version} - war - provided - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - ${project.version} - classes - provided - - - - - ch.qos.logback - logback-classic - - - - - javax.servlet - javax.servlet-api - provided - - - - - org.thymeleaf - thymeleaf - - - - - org.ebaysf.web - cors-filter - - - servlet-api - javax.servlet - - - - - - - org.springframework - spring-web - - - - - org.apache.commons - commons-dbcp2 - - - - org.postgresql - postgresql - 42.2.2 - - - - org.bitbucket.b_c - jose4j - 0.6.3 - - - - org.json - json - 20180130 - - - - org.springframework.boot - spring-boot-starter-security - 2.0.2.RELEASE - - - org.springframework.security.oauth - spring-security-oauth2 - 2.3.3.RELEASE - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.9.4 - - - - - org.apache.derby - derby - - - org.apache.derby - derbynet - - - org.apache.derby - derbyclient - - - - - org.eclipse.jetty - jetty-servlets - test - - - org.eclipse.jetty - jetty-servlet - test - - - org.eclipse.jetty.websocket - websocket-server - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-util - test - - - org.eclipse.jetty - jetty-webapp - test - - - com.helger - ph-schematron - - - Saxon-HE - net.sf.saxon - - - - - - - javax.interceptor - javax.interceptor-api - provided - - - - ca.uhn.hapi.fhir - hapi-fhir-test-utilities - ${project.version} - test - - - - - - - - - hapi-fhir-jpaserver-example - - - - - - org.eclipse.jetty - jetty-maven-plugin - - - /hapi-fhir-jpaserver-example - true - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - - - org.apache.maven.plugins - maven-war-plugin - - - - ${maven.build.timestamp} - - - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - - - src/main/webapp/WEB-INF/web.xml - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - - - - - - - - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java deleted file mode 100644 index 473a7c0456b..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ /dev/null @@ -1,132 +0,0 @@ -package ca.uhn.fhir.jpa.demo; - -import java.util.Properties; - -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; -import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect; -import org.apache.commons.dbcp2.BasicDataSource; -import org.hl7.fhir.dstu2.model.Subscription; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; - -/** - * This is the primary configuration file for the example server - */ -@Configuration -@EnableTransactionManagement() -public class FhirServerConfig extends BaseJavaConfigDstu3 { - - /** - * Configure FHIR properties around the the JPA server via this bean - */ - @Bean - public DaoConfig daoConfig() { - DaoConfig retVal = new DaoConfig(); - retVal.setAllowMultipleDelete(true); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); - retVal.setSubscriptionMatchingEnabled(true); - return retVal; - } - - @Bean - public ModelConfig modelConfig() { - return daoConfig().getModelConfig(); - } - - /** - * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a - * directory called "jpaserver_derby_files". - * - * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. - */ - @Bean(destroyMethod = "close") - public BasicDataSource dataSource() { - BasicDataSource retVal = new BasicDataSource(); - retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true"); - retVal.setUsername(""); - retVal.setPassword(""); - return retVal; - } - - @Override - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); - retVal.setPersistenceUnitName("HAPI_PU"); - retVal.setDataSource(dataSource()); - retVal.setJpaProperties(jpaProperties()); - return retVal; - } - - private Properties jpaProperties() { - Properties extraProperties = new Properties(); - extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName()); - extraProperties.put("hibernate.format_sql", "true"); - extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); - extraProperties.put("hibernate.jdbc.batch_size", "20"); - extraProperties.put("hibernate.cache.use_query_cache", "false"); - extraProperties.put("hibernate.cache.use_second_level_cache", "false"); - extraProperties.put("hibernate.cache.use_structured_entries", "false"); - extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); - extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); - extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); - extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); -// extraProperties.put("hibernate.search.default.worker.execution", "async"); - return extraProperties; - } - - /** - * Do some fancy logging to create a nice access log that has details about each incoming request. - */ - public LoggingInterceptor loggingInterceptor() { - LoggingInterceptor retVal = new LoggingInterceptor(); - retVal.setLoggerName("fhirtest.access"); - retVal.setMessageFormat( - "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); - retVal.setLogExceptions(true); - retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}"); - return retVal; - } - - /** - * This interceptor adds some pretty syntax highlighting in responses when a browser is detected - */ - @Bean(autowire = Autowire.BY_TYPE) - public ResponseHighlighterInterceptor responseHighlighterInterceptor() { - ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor(); - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public IServerInterceptor subscriptionSecurityInterceptor() { - SubscriptionsRequireManualActivationInterceptorDstu3 retVal = new SubscriptionsRequireManualActivationInterceptorDstu3(); - return retVal; - } - - @Bean - public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } -} diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java deleted file mode 100644 index 606a43f5756..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java +++ /dev/null @@ -1,124 +0,0 @@ -package ca.uhn.fhir.jpa.demo; - -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; -import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; -import org.apache.commons.dbcp2.BasicDataSource; -import org.apache.commons.lang3.time.DateUtils; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; -import java.util.Properties; - -/** - * This is the primary configuration file for the example server - */ -@Configuration -@EnableTransactionManagement() -public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 { - - /** - * Configure FHIR properties around the the JPA server via this bean - */ - @Bean - public DaoConfig daoConfig() { - DaoConfig retVal = new DaoConfig(); - retVal.setSubscriptionEnabled(true); - retVal.setSubscriptionPollDelay(5000); - retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); - retVal.setAllowMultipleDelete(true); - return retVal; - } - - /** - * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a - * directory called "jpaserver_derby_files". - * - * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. - */ - @Bean(destroyMethod = "close") - public DataSource dataSource() { - BasicDataSource retVal = new BasicDataSource(); - retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true"); - retVal.setUsername(""); - retVal.setPassword(""); - return retVal; - } - - @Override - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); - retVal.setPersistenceUnitName("HAPI_PU"); - retVal.setDataSource(dataSource()); - retVal.setJpaProperties(jpaProperties()); - return retVal; - } - - private Properties jpaProperties() { - Properties extraProperties = new Properties(); - extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName()); - extraProperties.put("hibernate.format_sql", "true"); - extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); - extraProperties.put("hibernate.jdbc.batch_size", "20"); - extraProperties.put("hibernate.cache.use_query_cache", "false"); - extraProperties.put("hibernate.cache.use_second_level_cache", "false"); - extraProperties.put("hibernate.cache.use_structured_entries", "false"); - extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); - extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); - extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); - extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); -// extraProperties.put("hibernate.search.default.worker.execution", "async"); - return extraProperties; - } - - /** - * Do some fancy logging to create a nice access log that has details about each incoming request. - */ - public LoggingInterceptor loggingInterceptor() { - LoggingInterceptor retVal = new LoggingInterceptor(); - retVal.setLoggerName("fhirtest.access"); - retVal.setMessageFormat( - "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); - retVal.setLogExceptions(true); - retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}"); - return retVal; - } - - /** - * This interceptor adds some pretty syntax highlighting in responses when a browser is detected - */ - @Bean(autowire = Autowire.BY_TYPE) - public ResponseHighlighterInterceptor responseHighlighterInterceptor() { - ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor(); - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public IServerInterceptor subscriptionSecurityInterceptor() { - SubscriptionsRequireManualActivationInterceptorDstu2 retVal = new SubscriptionsRequireManualActivationInterceptorDstu2(); - return retVal; - } - - @Bean - public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } - -} diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfig.java deleted file mode 100644 index 3d260917aac..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfig.java +++ /dev/null @@ -1,56 +0,0 @@ -package ca.uhn.fhir.jpa.demo; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.to.FhirTesterMvcConfig; -import ca.uhn.fhir.to.TesterConfig; - -//@formatter:off -/** - * This spring config file configures the web testing module. It serves two - * purposes: - * 1. It imports FhirTesterMvcConfig, which is the spring config for the - * tester itself - * 2. It tells the tester which server(s) to talk to, via the testerConfig() - * method below - */ -@Configuration -@Import(FhirTesterMvcConfig.class) -public class FhirTesterConfig { - - /** - * This bean tells the testing webpage which servers it should configure itself - * to communicate with. In this example we configure it to talk to the local - * server, as well as one public server. If you are creating a project to - * deploy somewhere else, you might choose to only put your own server's - * address here. - * - * Note the use of the ${serverBase} variable below. This will be replaced with - * the base URL as reported by the server itself. Often for a simple Tomcat - * (or other container) installation, this will end up being something - * like "http://localhost:8080/hapi-fhir-jpaserver-example". If you are - * deploying your server to a place with a fully qualified domain name, - * you might want to use that instead of using the variable. - */ - @Bean - public TesterConfig testerConfig() { - TesterConfig retVal = new TesterConfig(); - retVal - .addServer() - .withId("home") - .withFhirVersion(FhirVersionEnum.DSTU3) - .withBaseUrl("${serverBase}/baseDstu3") - .withName("Local Tester") - .addServer() - .withId("hapi") - .withFhirVersion(FhirVersionEnum.DSTU3) - .withBaseUrl("http://fhirtest.uhn.ca/baseDstu3") - .withName("Public HAPI Test Server"); - return retVal; - } - -} -//@formatter:on diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfigDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfigDstu2.java deleted file mode 100644 index 7ea3736a840..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirTesterConfigDstu2.java +++ /dev/null @@ -1,56 +0,0 @@ -package ca.uhn.fhir.jpa.demo; - -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.to.FhirTesterMvcConfig; -import ca.uhn.fhir.to.TesterConfig; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -//@formatter:off - -/** - * This spring config file configures the web testing module. It serves two - * purposes: - * 1. It imports FhirTesterMvcConfig, which is the spring config for the - * tester itself - * 2. It tells the tester which server(s) to talk to, via the testerConfig() - * method below - */ -@Configuration -@Import(FhirTesterMvcConfig.class) -public class FhirTesterConfigDstu2 { - - /** - * This bean tells the testing webpage which servers it should configure itself - * to communicate with. In this example we configure it to talk to the local - * server, as well as one public server. If you are creating a project to - * deploy somewhere else, you might choose to only put your own server's - * address here. - * - * Note the use of the ${serverBase} variable below. This will be replaced with - * the base URL as reported by the server itself. Often for a simple Tomcat - * (or other container) installation, this will end up being something - * like "http://localhost:8080/hapi-fhir-jpaserver-example". If you are - * deploying your server to a place with a fully qualified domain name, - * you might want to use that instead of using the variable. - */ - @Bean - public TesterConfig testerConfig() { - TesterConfig retVal = new TesterConfig(); - retVal - .addServer() - .withId("home") - .withFhirVersion(FhirVersionEnum.DSTU2) - .withBaseUrl("${serverBase}/baseDstu2") - .withName("Local Tester") - .addServer() - .withId("hapi") - .withFhirVersion(FhirVersionEnum.DSTU2) - .withBaseUrl("http://fhirtest.uhn.ca/baseDstu2") - .withName("Public HAPI Test Server"); - return retVal; - } - -} -//@formatter:on diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java deleted file mode 100644 index 38506ddd976..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ /dev/null @@ -1,155 +0,0 @@ - -package ca.uhn.fhir.jpa.demo; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; -import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; -import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; -import ca.uhn.fhir.model.dstu2.composite.MetaDt; -import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - -import javax.servlet.ServletException; -import java.util.List; - -public class JpaServerDemo extends RestfulServer { - - private static final long serialVersionUID = 1L; - - private WebApplicationContext myAppCtx; - - @SuppressWarnings("unchecked") - @Override - protected void initialize() throws ServletException { - super.initialize(); - - /* - * We want to support FHIR DSTU2 format. This means that the server - * will use the DSTU2 bundle format and other DSTU2 encoding changes. - * - * If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1 - */ - FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU3; - setFhirContext(new FhirContext(fhirVersion)); - - // Get the spring context from the web container (it's declared in web.xml) - myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext(); - - /* - * The BaseJavaConfigDstu2.java class is a spring configuration - * file which is automatically generated as a part of hapi-fhir-jpaserver-base and - * contains bean definitions for a resource provider for each resource type - */ - String resourceProviderBeanName; - if (fhirVersion == FhirVersionEnum.DSTU2) { - resourceProviderBeanName = "myResourceProvidersDstu2"; - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - resourceProviderBeanName = "myResourceProvidersDstu3"; - } else { - throw new IllegalStateException(); - } - List beans = myAppCtx.getBean(resourceProviderBeanName, List.class); - setResourceProviders(beans); - - /* - * The system provider implements non-resource-type methods, such as - * transaction, and global history. - */ - Object systemProvider; - if (fhirVersion == FhirVersionEnum.DSTU2) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); - } else { - throw new IllegalStateException(); - } - setPlainProviders(systemProvider); - - /* - * The conformance provider exports the supported resources, search parameters, etc for - * this server. The JPA version adds resource counts to the exported statement, so it - * is a nice addition. - */ - if (fhirVersion == FhirVersionEnum.DSTU2) { - IFhirSystemDao systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class); - JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("Example Server"); - setServerConformanceProvider(confProvider); - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - IFhirSystemDao systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("Example Server"); - setServerConformanceProvider(confProvider); - } else { - throw new IllegalStateException(); - } - - /* - * Enable ETag Support (this is already the default) - */ - setETagSupport(ETagSupportEnum.ENABLED); - - /* - * This server tries to dynamically generate narratives - */ - FhirContext ctx = getFhirContext(); - ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); - - /* - * Default to JSON and pretty printing - */ - setDefaultPrettyPrint(true); - setDefaultResponseEncoding(EncodingEnum.JSON); - - /* - * -- New in HAPI FHIR 1.5 -- - * This configures the server to page search results to and from - * the database, instead of only paging them to memory. This may mean - * a performance hit when performing searches that return lots of results, - * but makes the server much more scalable. - */ - setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); - - /* - * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() - */ - SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); - subscriptionInterceptorLoader.registerInterceptors(); - - /* - * If you are hosting this server at a specific DNS name, the server will try to - * figure out the FHIR base URL based on what the web container tells it, but - * this doesn't always work. If you are setting links in your search bundles that - * just refer to "localhost", you might want to use a server address strategy: - */ - //setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2")); - - /* - * If you are using DSTU3+, you may want to add a terminology uploader, which allows - * uploading of external terminologies such as Snomed CT. Note that this uploader - * does not have any security attached (any anonymous user may use it by default) - * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor - * with this feature. - */ - if (fhirVersion == FhirVersionEnum.DSTU3) { - registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class)); - } - } - -} diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java deleted file mode 100644 index 3c802e6cb10..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java +++ /dev/null @@ -1,154 +0,0 @@ - -package ca.uhn.fhir.jpa.demo; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; -import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; -import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; -import ca.uhn.fhir.model.dstu2.composite.MetaDt; -import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - -import javax.servlet.ServletException; -import java.util.List; - -public class JpaServerDemoDstu2 extends RestfulServer { - - private static final long serialVersionUID = 1L; - - private WebApplicationContext myAppCtx; - - @SuppressWarnings("unchecked") - @Override - protected void initialize() throws ServletException { - super.initialize(); - - /* - * We want to support FHIR DSTU2 format. This means that the server - * will use the DSTU2 bundle format and other DSTU2 encoding changes. - * - * If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1 - */ - FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2; - setFhirContext(new FhirContext(fhirVersion)); - - // Get the spring context from the web container (it's declared in web.xml) - myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext(); - - /* - * The BaseJavaConfigDstu2.java class is a spring configuration - * file which is automatically generated as a part of hapi-fhir-jpaserver-base and - * contains bean definitions for a resource provider for each resource type - */ - String resourceProviderBeanName; - if (fhirVersion == FhirVersionEnum.DSTU2) { - resourceProviderBeanName = "myResourceProvidersDstu2"; - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - resourceProviderBeanName = "myResourceProvidersDstu3"; - } else { - throw new IllegalStateException(); - } - List beans = myAppCtx.getBean(resourceProviderBeanName, List.class); - setResourceProviders(beans); - - /* - * The system provider implements non-resource-type methods, such as - * transaction, and global history. - */ - Object systemProvider; - if (fhirVersion == FhirVersionEnum.DSTU2) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); - } else { - throw new IllegalStateException(); - } - setPlainProviders(systemProvider); - - /* - * The conformance provider exports the supported resources, search parameters, etc for - * this server. The JPA version adds resource counts to the exported statement, so it - * is a nice addition. - */ - if (fhirVersion == FhirVersionEnum.DSTU2) { - IFhirSystemDao systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class); - JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("Example Server"); - setServerConformanceProvider(confProvider); - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - IFhirSystemDao systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - myAppCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("Example Server"); - setServerConformanceProvider(confProvider); - } else { - throw new IllegalStateException(); - } - - /* - * Enable ETag Support (this is already the default) - */ - setETagSupport(ETagSupportEnum.ENABLED); - - /* - * This server tries to dynamically generate narratives - */ - FhirContext ctx = getFhirContext(); - ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); - - /* - * Default to JSON and pretty printing - */ - setDefaultPrettyPrint(true); - setDefaultResponseEncoding(EncodingEnum.JSON); - - /* - * -- New in HAPI FHIR 1.5 -- - * This configures the server to page search results to and from - * the database, instead of only paging them to memory. This may mean - * a performance hit when performing searches that return lots of results, - * but makes the server much more scalable. - */ - setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); - - /* - * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() - */ - SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); - subscriptionInterceptorLoader.registerInterceptors(); - - /* - * If you are hosting this server at a specific DNS name, the server will try to - * figure out the FHIR base URL based on what the web container tells it, but - * this doesn't always work. If you are setting links in your search bundles that - * just refer to "localhost", you might want to use a server address strategy: - */ - //setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2")); - - /* - * If you are using DSTU3+, you may want to add a terminology uploader, which allows - * uploading of external terminologies such as Snomed CT. Note that this uploader - * does not have any security attached (any anonymous user may use it by default) - * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor - * with this feature. - */ - //if (fhirVersion == FhirVersionEnum.DSTU3) { - // registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class)); - //} - } - -} diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/elasticsearch/FhirServerConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/elasticsearch/FhirServerConfig.java deleted file mode 100644 index 363f8708e0e..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/elasticsearch/FhirServerConfig.java +++ /dev/null @@ -1,110 +0,0 @@ -package ca.uhn.fhir.jpa.demo.elasticsearch; - -import java.util.Properties; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.ElasticsearchMappingProvider; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; -import org.apache.commons.dbcp2.BasicDataSource; -import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -/** - * This is the configuration file for the example server integrating the ElasticSearch engine. - */ -@Configuration -@EnableTransactionManagement() -public class FhirServerConfig extends BaseJavaConfigDstu3 { - - /** - * Configure FHIR properties around the the JPA server via this bean - */ - @Bean - public DaoConfig daoConfig() { - DaoConfig retVal = new DaoConfig(); - retVal.setAllowMultipleDelete(true); - return retVal; - } - - /** - * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a - * directory called "jpaserver_derby_files". - * - * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. - */ - @Bean(destroyMethod = "close") - public DataSource dataSource() { - BasicDataSource retVal = new BasicDataSource(); - retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); - retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true"); - retVal.setUsername(""); - retVal.setPassword(""); - return retVal; - } - - @Override - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); - retVal.setPersistenceUnitName("HAPI_PU"); - retVal.setDataSource(dataSource()); - retVal.setJpaProperties(jpaProperties()); - return retVal; - } - - private Properties jpaProperties() { - Properties extraProperties = new Properties(); - extraProperties.put("hibernate.dialect", ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect.class.getName()); - extraProperties.put("hibernate.format_sql", "true"); - extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); - extraProperties.put("hibernate.jdbc.batch_size", "20"); - extraProperties.put("hibernate.cache.use_query_cache", "false"); - extraProperties.put("hibernate.cache.use_second_level_cache", "false"); - extraProperties.put("hibernate.cache.use_structured_entries", "false"); - extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - - // the belowing properties are used for ElasticSearch integration - extraProperties.put(ElasticsearchEnvironment.ANALYSIS_DEFINITION_PROVIDER, ElasticsearchMappingProvider.class.getName()); - extraProperties.put("hibernate.search.default.indexmanager", "elasticsearch"); - extraProperties.put("hibernate.search.default.elasticsearch.host", "http://127.0.0.1:9200"); - extraProperties.put("hibernate.search.default.elasticsearch.index_schema_management_strategy", "CREATE"); - extraProperties.put("hibernate.search.default.elasticsearch.index_management_wait_timeout", "10000"); - extraProperties.put("hibernate.search.default.elasticsearch.required_index_status", "yellow"); - return extraProperties; - } - - /** - * This interceptor adds some pretty syntax highlighting in responses when a browser is detected - * @return - */ - @Bean(autowire = Autowire.BY_TYPE) - public ResponseHighlighterInterceptor responseHighlighterInterceptor() { - ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor(); - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public IServerInterceptor subscriptionSecurityInterceptor() { - SubscriptionsRequireManualActivationInterceptorDstu3 retVal = new SubscriptionsRequireManualActivationInterceptorDstu3(); - return retVal; - } - - @Bean - public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } - -} diff --git a/hapi-fhir-jpaserver-example/src/main/resources/logback.xml b/hapi-fhir-jpaserver-example/src/main/resources/logback.xml deleted file mode 100644 index ffec8d30c06..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/resources/logback.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - INFO - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n - - - - - - - - \ No newline at end of file diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/about.html b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/about.html deleted file mode 100644 index d552027e956..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/about.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - About This Server - - - -
        -
        - -
        -
        -
        - -
        - -
        - -
        -
        -

        About This Server

        -
        -
        -
        - -
        -

        - This server provides a nearly complete implementation of the FHIR Specification - using a 100% open source software stack. It is hosted by University Health Network. -

        -

        - The architecture in use here is shown in the image on the right. This server is built - from a number of modules of the - HAPI FHIR - project, which is a 100% open-source (Apache 2.0 Licensed) Java based - implementation of the FHIR specification. -

        -

        - -

        -
        -
        -
        -
        -

        Data On This Server

        -
        -
        -

        - This server is regularly loaded with a standard set of test data sourced - from UHN's own testing environment. Do not use this server to store any data - that you will need later, as we will be regularly resetting it. -

        -

        - This is not a production server and it provides no privacy. Do not store any - confidential data here. -

        -
        -
        - -
        -
        -
        - -
        -
        - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-footer.html b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-footer.html deleted file mode 100644 index 1c4f8c75651..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-footer.html +++ /dev/null @@ -1,5 +0,0 @@ - - -
        -
        - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html deleted file mode 100644 index dfc4769d6e4..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ /dev/null @@ -1,52 +0,0 @@ - - -
        - -

        - This is the home for the FHIR test server operated by - University Health Network. This server - (and the testing application you are currently using to access it) - is entirely built using - HAPI-FHIR, - a 100% open-source Java implementation of the - FHIR specification. -

        -

        - Here are some things you might wish to try: -

        -
          -
        • - View a - list of patients - on this server. -
        • -
        • - Construct a - search query - on this server. -
        • -
        • - Access a - different server - (use the Server menu at the top of the page to see a list of public FHIR servers) -
        • -
        -
        - -

        - You are accessing the public FHIR server - . This server is hosted elsewhere on the internet - but is being accessed using the HAPI client implementation. -

        -
        -

        - - - This is not a production server! - - Do not store any information here that contains personal health information - or any other confidential information. This server will be regularly purged - and reloaded with fixed test data. -

        -
        - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web-dstu2.xml b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web-dstu2.xml deleted file mode 100644 index de1a573d1c4..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web-dstu2.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - org.springframework.web.context.ContextLoaderListener - - - contextClass - - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - - contextConfigLocation - - ca.uhn.fhir.jpa.demo.FhirServerConfigDstu2 - - - - - - - spring - org.springframework.web.servlet.DispatcherServlet - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu2 - - 2 - - - - fhirServlet - ca.uhn.fhir.jpa.demo.JpaServerDemoDstu2 - - ImplementationDescription - FHIR JPA Server - - - FhirVersion - DSTU2 - - 1 - - - - fhirServlet - /baseDstu2/* - - - - spring - / - - - - - - - CORS Filter - org.ebaysf.web.cors.CORSFilter - - A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials. - cors.allowed.origins - * - - - A comma separated list of HTTP verbs, using which a CORS request can be made. - cors.allowed.methods - GET,POST,PUT,DELETE,OPTIONS - - - A comma separated list of allowed headers when making a non simple CORS request. - cors.allowed.headers - X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers - - - A comma separated list non-standard response headers that will be exposed to XHR2 object. - cors.exposed.headers - Location,Content-Location - - - A flag that suggests if CORS is supported with cookies - cors.support.credentials - true - - - A flag to control logging - cors.logging.enabled - true - - - Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache. - cors.preflight.maxage - 300 - - - - CORS Filter - /* - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web.xml b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index a09387d37ea..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - org.springframework.web.context.ContextLoaderListener - - - contextClass - - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - - contextConfigLocation - - ca.uhn.fhir.jpa.demo.FhirServerConfig - - - - - - - spring - org.springframework.web.servlet.DispatcherServlet - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - ca.uhn.fhir.jpa.demo.FhirTesterConfig - - 2 - - - - fhirServlet - ca.uhn.fhir.jpa.demo.JpaServerDemo - - ImplementationDescription - FHIR JPA Server - - - FhirVersion - DSTU3 - - 1 - - - - fhirServlet - /baseDstu3/* - - - - spring - / - - - - - - - CORS Filter - org.ebaysf.web.cors.CORSFilter - - A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials. - cors.allowed.origins - * - - - A comma separated list of HTTP verbs, using which a CORS request can be made. - cors.allowed.methods - GET,POST,PUT,DELETE,OPTIONS - - - A comma separated list of allowed headers when making a non simple CORS request. - cors.allowed.headers - Accept,Access-Control-Request-Headers,Access-Control-Request-Method,Cache-Control,Content-Type,Origin,Prefer,X-FHIR-Starter,X-Requested-With - - - A comma separated list non-standard response headers that will be exposed to XHR2 object. - cors.exposed.headers - Location,Content-Location - - - A flag that suggests if CORS is supported with cookies - cors.support.credentials - true - - - A flag to control logging - cors.logging.enabled - true - - - Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache. - cors.preflight.maxage - 300 - - - - CORS Filter - /* - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/javaee_6.xsd b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/javaee_6.xsd deleted file mode 100644 index 9fb587749ce..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/javaee_6.xsd +++ /dev/null @@ -1,2419 +0,0 @@ - - - - - - DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - - Copyright 2003-2009 Sun Microsystems, Inc. All rights reserved. - - The contents of this file are subject to the terms of either the - GNU General Public License Version 2 only ("GPL") or the Common - Development and Distribution License("CDDL") (collectively, the - "License"). You may not use this file except in compliance with - the License. You can obtain a copy of the License at - https://glassfish.dev.java.net/public/CDDL+GPL.html or - glassfish/bootstrap/legal/LICENSE.txt. See the License for the - specific language governing permissions and limitations under the - License. - - When distributing the software, include this License Header - Notice in each file and include the License file at - glassfish/bootstrap/legal/LICENSE.txt. Sun designates this - particular file as subject to the "Classpath" exception as - provided by Sun in the GPL Version 2 section of the License file - that accompanied this code. If applicable, add the following - below the License Header, with the fields enclosed by brackets [] - replaced by your own identifying information: - "Portions Copyrighted [year] [name of copyright owner]" - - Contributor(s): - - If you wish your version of this file to be governed by only the - CDDL or only the GPL Version 2, indicate your decision by adding - "[Contributor] elects to include this software in this - distribution under the [CDDL or GPL Version 2] license." If you - don't indicate a single choice of license, a recipient has the - option to distribute your version of this file under either the - CDDL, the GPL Version 2 or to extend the choice of license to its - licensees as provided above. However, if you add GPL Version 2 - code and therefore, elected the GPL Version 2 license, then the - option applies only if the new code is made subject to such - option by the copyright holder. - - - - - - - - The following definitions that appear in the common - shareable schema(s) of Java EE deployment descriptors should be - interpreted with respect to the context they are included: - - Deployment Component may indicate one of the following: - java ee application; - application client; - web application; - enterprise bean; - resource adapter; - - Deployment File may indicate one of the following: - ear file; - war file; - jar file; - rar file; - - - - - - - - - - - This group keeps the usage of the contained description related - elements consistent across Java EE deployment descriptors. - - All elements may occur multiple times with different languages, - to support localization of the content. - - - - - - - - - - - - - - - This group keeps the usage of the contained JNDI environment - reference elements consistent across Java EE deployment descriptors. - - - - - - - - - - - - - - - - - - - - - - - This group collects elements that are common to most - JNDI resource elements. - - - - - - - - - - The JNDI name to be looked up to resolve a resource reference. - - - - - - - - - - - - This group collects elements that are common to all the - JNDI resource elements. It does not include the lookup-name - element, that is only applicable to some resource elements. - - - - - - - - - A product specific name that this resource should be - mapped to. The name of this resource, as defined by the - resource's name element or defaulted, is a name that is - local to the application component using the resource. - (It's a name in the JNDI java:comp/env namespace.) Many - application servers provide a way to map these local - names to names of resources known to the application - server. This mapped name is often a global JNDI name, - but may be a name of any form. - - Application servers are not required to support any - particular form or type of mapped name, nor the ability - to use mapped names. The mapped name is - product-dependent and often installation-dependent. No - use of a mapped name is portable. - - - - - - - - - - - - - - - - Configuration of a DataSource. - - - - - - - - - Description of this DataSource. - - - - - - - - - The name element specifies the JNDI name of the - data source being defined. - - - - - - - - - DataSource, XADataSource or ConnectionPoolDataSource - implementation class. - - - - - - - - - Database server name. - - - - - - - - - Port number where a server is listening for requests. - - - - - - - - - Name of a database on a server. - - - - - - - - url
        property is specified - along with other standard DataSource properties - such as serverName, databaseName - and portNumber, the more specific properties will - take precedence and url will be ignored. - - ]]> - - - - - - - - User name to use for connection authentication. - - - - - - - - - Password to use for connection authentication. - - - - - - - - - JDBC DataSource property. This may be a vendor-specific - property or a less commonly used DataSource property. - - - - - - - - - Sets the maximum time in seconds that this data source - will wait while attempting to connect to a database. - - - - - - - - - Set to false if connections should not participate in - transactions. - - - - - - - - - Isolation level for connections. - - - - - - - - - Number of connections that should be created when a - connection pool is initialized. - - - - - - - - - Maximum number of connections that should be concurrently - allocated for a connection pool. - - - - - - - - - Minimum number of connections that should be concurrently - allocated for a connection pool. - - - - - - - - - The number of seconds that a physical connection should - remain unused in the pool before the connection is - closed for a connection pool. - - - - - - - - - The total number of statements that a connection pool - should keep open. - - - - - - - - - - - - - - - - The description type is used by a description element to - provide text describing the parent element. The elements - that use this type should include any information that the - Deployment Component's Deployment File file producer wants - to provide to the consumer of the Deployment Component's - Deployment File (i.e., to the Deployer). Typically, the - tools used by such a Deployment File consumer will display - the description when processing the parent element that - contains the description. - - The lang attribute defines the language that the - description is provided in. The default value is "en" (English). - - - - - - - - - - - - - - - This type defines a dewey decimal that is used - to describe versions of documents. - - - - - - - - - - - - - - - - Employee Self Service - - - The value of the xml:lang attribute is "en" (English) by default. - - ]]> - - - - - - - - - - - - - - - - EmployeeRecord - - ../products/product.jar#ProductEJB - - ]]> - - - - - - - - - - - - - - - The ejb-local-refType is used by ejb-local-ref elements for - the declaration of a reference to an enterprise bean's local - home or to the local business interface of a 3.0 bean. - The declaration consists of: - - - an optional description - - the EJB reference name used in the code of the Deployment - Component that's referencing the enterprise bean. - - the optional expected type of the referenced enterprise bean - - the optional expected local interface of the referenced - enterprise bean or the local business interface of the - referenced enterprise bean. - - the optional expected local home interface of the referenced - enterprise bean. Not applicable if this ejb-local-ref refers - to the local business interface of a 3.0 bean. - - optional ejb-link information, used to specify the - referenced enterprise bean - - optional elements to define injection of the named enterprise - bean into a component field or property. - - - - - - - - - - - - - - - - - - - - - - ejb/Payroll - - ]]> - - - - - - - - - - - - - - - The ejb-refType is used by ejb-ref elements for the - declaration of a reference to an enterprise bean's home or - to the remote business interface of a 3.0 bean. - The declaration consists of: - - - an optional description - - the EJB reference name used in the code of - the Deployment Component that's referencing the enterprise - bean. - - the optional expected type of the referenced enterprise bean - - the optional remote interface of the referenced enterprise bean - or the remote business interface of the referenced enterprise - bean - - the optional expected home interface of the referenced - enterprise bean. Not applicable if this ejb-ref - refers to the remote business interface of a 3.0 bean. - - optional ejb-link information, used to specify the - referenced enterprise bean - - optional elements to define injection of the named enterprise - bean into a component field or property - - - - - - - - - - - - - - - - - - - - - - - The ejb-ref-typeType contains the expected type of the - referenced enterprise bean. - - The ejb-ref-type designates a value - that must be one of the following: - - Entity - Session - - - - - - - - - - - - - - - - - - - This type is used to designate an empty - element when used. - - - - - - - - - - - - - - The env-entryType is used to declare an application's - environment entry. The declaration consists of an optional - description, the name of the environment entry, a type - (optional if the value is injected, otherwise required), and - an optional value. - - It also includes optional elements to define injection of - the named resource into fields or JavaBeans properties. - - If a value is not specified and injection is requested, - no injection will occur and no entry of the specified name - will be created. This allows an initial value to be - specified in the source code without being incorrectly - changed when no override has been specified. - - If a value is not specified and no injection is requested, - a value must be supplied during deployment. - - This type is used by env-entry elements. - - - - - - - - - minAmount - - ]]> - - - - - - - java.lang.Integer - - ]]> - - - - - - - 100.00 - - ]]> - - - - - - - - - - - - - - - java.lang.Boolean - java.lang.Class - com.example.Color - - ]]> - - - - - - - - - - - - - - - The elements that use this type designate the name of a - Java class or interface. The name is in the form of a - "binary name", as defined in the JLS. This is the form - of name used in Class.forName(). Tools that need the - canonical name (the name used in source code) will need - to convert this binary name to the canonical name. - - - - - - - - - - - - - - - - This type defines four different values which can designate - boolean values. This includes values yes and no which are - not designated by xsd:boolean - - - - - - - - - - - - - - - - - - - - - The icon type contains small-icon and large-icon elements - that specify the file names for small and large GIF, JPEG, - or PNG icon images used to represent the parent element in a - GUI tool. - - The xml:lang attribute defines the language that the - icon file names are provided in. Its value is "en" (English) - by default. - - - - - - - - employee-service-icon16x16.jpg - - ]]> - - - - - - - employee-service-icon32x32.jpg - - ]]> - - - - - - - - - - - - - - - - An injection target specifies a class and a name within - that class into which a resource should be injected. - - The injection target class specifies the fully qualified - class name that is the target of the injection. The - Java EE specifications describe which classes can be an - injection target. - - The injection target name specifies the target within - the specified class. The target is first looked for as a - JavaBeans property name. If not found, the target is - looked for as a field name. - - The specified resource will be injected into the target - during initialization of the class by either calling the - set method for the target property or by setting a value - into the named field. - - - - - - - - - - - - - - The following transaction isolation levels are allowed - (see documentation for the java.sql.Connection interface): - TRANSACTION_READ_UNCOMMITTED - TRANSACTION_READ_COMMITTED - TRANSACTION_REPEATABLE_READ - TRANSACTION_SERIALIZABLE - - - - - - - - - - - - - - - - - - - The java-identifierType defines a Java identifier. - The users of this type should further verify that - the content does not contain Java reserved keywords. - - - - - - - - - - - - - - - - - - This is a generic type that designates a Java primitive - type or a fully qualified name of a Java interface/type, - or an array of such types. - - - - - - - - - - - - - - - - - : - - Example: - - jdbc:mysql://localhost:3307/testdb - - ]]> - - - - - - - - - - - - - - - - - The jndi-nameType type designates a JNDI name in the - Deployment Component's environment and is relative to the - java:comp/env context. A JNDI name must be unique within the - Deployment Component. - - - - - - - - - - - - - - - com.aardvark.payroll.PayrollHome - - ]]> - - - - - - - - - - - - - - - The lifecycle-callback type specifies a method on a - class to be called when a lifecycle event occurs. - Note that each class may have only one lifecycle callback - method for any given event and that the method may not - be overloaded. - - If the lifefycle-callback-class element is missing then - the class defining the callback is assumed to be the - component class in scope at the place in the descriptor - in which the callback definition appears. - - - - - - - - - - - - - - - - - The listenerType indicates the deployment properties for a web - application listener bean. - - - - - - - - - - The listener-class element declares a class in the - application must be registered as a web - application listener bean. The value is the fully - qualified classname of the listener class. - - - - - - - - - - - - - - - - The localType defines the fully-qualified name of an - enterprise bean's local interface. - - - - - - - - - - - - - - - - The local-homeType defines the fully-qualified - name of an enterprise bean's local home interface. - - - - - - - - - - - - - - - - This type is a general type that can be used to declare - parameter/value lists. - - - - - - - - - - The param-name element contains the name of a - parameter. - - - - - - - - - The param-value element contains the value of a - parameter. - - - - - - - - - - - - - - - - The elements that use this type designate either a relative - path or an absolute path starting with a "/". - - In elements that specify a pathname to a file within the - same Deployment File, relative filenames (i.e., those not - starting with "/") are considered relative to the root of - the Deployment File's namespace. Absolute filenames (i.e., - those starting with "/") also specify names in the root of - the Deployment File's namespace. In general, relative names - are preferred. The exception is .war files where absolute - names are preferred for consistency with the Servlet API. - - - - - - - - - - - - - - - - myPersistenceContext - - - - - myPersistenceContext - - PersistenceUnit1 - - Extended - - - ]]> - - - - - - - - - The persistence-context-ref-name element specifies - the name of a persistence context reference; its - value is the environment entry name used in - Deployment Component code. The name is a JNDI name - relative to the java:comp/env context. - - - - - - - - - The Application Assembler(or BeanProvider) may use the - following syntax to avoid the need to rename persistence - units to have unique names within a Java EE application. - - The Application Assembler specifies the pathname of the - root of the persistence.xml file for the referenced - persistence unit and appends the name of the persistence - unit separated from the pathname by #. The pathname is - relative to the referencing application component jar file. - In this manner, multiple persistence units with the same - persistence unit name may be uniquely identified when the - Application Assembler cannot change persistence unit names. - - - - - - - - - - Used to specify properties for the container or persistence - provider. Vendor-specific properties may be included in - the set of properties. Properties that are not recognized - by a vendor must be ignored. Entries that make use of the - namespace javax.persistence and its subnamespaces must not - be used for vendor-specific properties. The namespace - javax.persistence is reserved for use by the specification. - - - - - - - - - - - - - - - - - The persistence-context-typeType specifies the transactional - nature of a persistence context reference. - - The value of the persistence-context-type element must be - one of the following: - Transaction - Extended - - - - - - - - - - - - - - - - - - - Specifies a name/value pair. - - - - - - - - - - - - - - - - - - - - myPersistenceUnit - - - - - myPersistenceUnit - - PersistenceUnit1 - - - - ]]> - - - - - - - - - The persistence-unit-ref-name element specifies - the name of a persistence unit reference; its - value is the environment entry name used in - Deployment Component code. The name is a JNDI name - relative to the java:comp/env context. - - - - - - - - - The Application Assembler(or BeanProvider) may use the - following syntax to avoid the need to rename persistence - units to have unique names within a Java EE application. - - The Application Assembler specifies the pathname of the - root of the persistence.xml file for the referenced - persistence unit and appends the name of the persistence - unit separated from the pathname by #. The pathname is - relative to the referencing application component jar file. - In this manner, multiple persistence units with the same - persistence unit name may be uniquely identified when the - Application Assembler cannot change persistence unit names. - - - - - - - - - - - - - - - - com.wombat.empl.EmployeeService - - ]]> - - - - - - - - - - - - - - - jms/StockQueue - - javax.jms.Queue - - - - ]]> - - - - - - - - - The resource-env-ref-name element specifies the name - of a resource environment reference; its value is - the environment entry name used in - the Deployment Component code. The name is a JNDI - name relative to the java:comp/env context and must - be unique within a Deployment Component. - - - - - - - - - The resource-env-ref-type element specifies the type - of a resource environment reference. It is the - fully qualified name of a Java language class or - interface. - - - - - - - - - - - - - - - - - jdbc/EmployeeAppDB - javax.sql.DataSource - Container - Shareable - - - ]]> - - - - - - - - - The res-ref-name element specifies the name of a - resource manager connection factory reference. - The name is a JNDI name relative to the - java:comp/env context. - The name must be unique within a Deployment File. - - - - - - - - - The res-type element specifies the type of the data - source. The type is specified by the fully qualified - Java language class or interface - expected to be implemented by the data source. - - - - - - - - - - - - - - - - - - - The res-authType specifies whether the Deployment Component - code signs on programmatically to the resource manager, or - whether the Container will sign on to the resource manager - on behalf of the Deployment Component. In the latter case, - the Container uses information that is supplied by the - Deployer. - - The value must be one of the two following: - - Application - Container - - - - - - - - - - - - - - - - - - - The res-sharing-scope type specifies whether connections - obtained through the given resource manager connection - factory reference can be shared. The value, if specified, - must be one of the two following: - - Shareable - Unshareable - - The default value is Shareable. - - - - - - - - - - - - - - - - - - - The run-asType specifies the run-as identity to be - used for the execution of a component. It contains an - optional description, and the name of a security role. - - - - - - - - - - - - - - - - - - The role-nameType designates the name of a security role. - - The name must conform to the lexical rules for a token. - - - - - - - - - - - - - - - - - This role includes all employees who are authorized - to access the employee service application. - - employee - - - ]]> - - - - - - - - - - - - - - - - - The security-role-refType contains the declaration of a - security role reference in a component's or a - Deployment Component's code. The declaration consists of an - optional description, the security role name used in the - code, and an optional link to a security role. If the - security role is not specified, the Deployer must choose an - appropriate security role. - - - - - - - - - - The value of the role-name element must be the String used - as the parameter to the - EJBContext.isCallerInRole(String roleName) method or the - HttpServletRequest.isUserInRole(String role) method. - - - - - - - - - The role-link element is a reference to a defined - security role. The role-link element must contain - the name of one of the security roles defined in the - security-role elements. - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:QName. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:boolean. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:NMTOKEN. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:anyURI. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:integer. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:positiveInteger. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:nonNegativeInteger. - - - - - - - - - - - - - - - - - - This type adds an "id" attribute to xsd:string. - - - - - - - - - - - - - - - - - - This is a special string datatype that is defined by Java EE as - a base type for defining collapsed strings. When schemas - require trailing/leading space elimination as well as - collapsing the existing whitespace, this base type may be - used. - - - - - - - - - - - - - - - - - - This simple type designates a boolean with only two - permissible values - - - true - - false - - - - - - - - - - - - - - - - - - The url-patternType contains the url pattern of the mapping. - It must follow the rules specified in Section 11.2 of the - Servlet API Specification. This pattern is assumed to be in - URL-decoded form and must not contain CR(#xD) or LF(#xA). - If it contains those characters, the container must inform - the developer with a descriptive error message. - The container must preserve all characters including whitespaces. - - - - - - - - - - - - - - - - CorporateStocks - - - - ]]> - - - - - - - - - The message-destination-name element specifies a - name for a message destination. This name must be - unique among the names of message destinations - within the Deployment File. - - - - - - - - - A product specific name that this message destination - should be mapped to. Each message-destination-ref - element that references this message destination will - define a name in the namespace of the referencing - component or in one of the other predefined namespaces. - Many application servers provide a way to map these - local names to names of resources known to the - application server. This mapped name is often a global - JNDI name, but may be a name of any form. Each of the - local names should be mapped to this same global name. - - Application servers are not required to support any - particular form or type of mapped name, nor the ability - to use mapped names. The mapped name is - product-dependent and often installation-dependent. No - use of a mapped name is portable. - - - - - - - - - The JNDI name to be looked up to resolve the message destination. - - - - - - - - - - - - - - - - jms/StockQueue - - javax.jms.Queue - - Consumes - - CorporateStocks - - - - ]]> - - - - - - - - - The message-destination-ref-name element specifies - the name of a message destination reference; its - value is the environment entry name used in - Deployment Component code. - - - - - - - - - - - - - - - - - - - - The message-destination-usageType specifies the use of the - message destination indicated by the reference. The value - indicates whether messages are consumed from the message - destination, produced for the destination, or both. The - Assembler makes use of this information in linking producers - of a destination with its consumers. - - The value of the message-destination-usage element must be - one of the following: - Consumes - Produces - ConsumesProduces - - - - - - - - - - - - - - - - - - - javax.jms.Queue - - - ]]> - - - - - - - - - - - - - - - The message-destination-linkType is used to link a message - destination reference or message-driven bean to a message - destination. - - The Assembler sets the value to reflect the flow of messages - between producers and consumers in the application. - - The value must be the message-destination-name of a message - destination in the same Deployment File or in another - Deployment File in the same Java EE application unit. - - Alternatively, the value may be composed of a path name - specifying a Deployment File containing the referenced - message destination with the message-destination-name of the - destination appended and separated from the path name by - "#". The path name is relative to the Deployment File - containing Deployment Component that is referencing the - message destination. This allows multiple message - destinations with the same name to be uniquely identified. - - - - - - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/jsp_2_2.xsd b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/jsp_2_2.xsd deleted file mode 100644 index fa41e4266f1..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/jsp_2_2.xsd +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - - Copyright 2003-2009 Sun Microsystems, Inc. All rights reserved. - - The contents of this file are subject to the terms of either the - GNU General Public License Version 2 only ("GPL") or the Common - Development and Distribution License("CDDL") (collectively, the - "License"). You may not use this file except in compliance with - the License. You can obtain a copy of the License at - https://glassfish.dev.java.net/public/CDDL+GPL.html or - glassfish/bootstrap/legal/LICENSE.txt. See the License for the - specific language governing permissions and limitations under the - License. - - When distributing the software, include this License Header - Notice in each file and include the License file at - glassfish/bootstrap/legal/LICENSE.txt. Sun designates this - particular file as subject to the "Classpath" exception as - provided by Sun in the GPL Version 2 section of the License file - that accompanied this code. If applicable, add the following - below the License Header, with the fields enclosed by brackets [] - replaced by your own identifying information: - "Portions Copyrighted [year] [name of copyright owner]" - - Contributor(s): - - If you wish your version of this file to be governed by only the - CDDL or only the GPL Version 2, indicate your decision by adding - "[Contributor] elects to include this software in this - distribution under the [CDDL or GPL Version 2] license." If you - don't indicate a single choice of license, a recipient has the - option to distribute your version of this file under either the - CDDL, the GPL Version 2 or to extend the choice of license to its - licensees as provided above. However, if you add GPL Version 2 - code and therefore, elected the GPL Version 2 license, then the - option applies only if the new code is made subject to such - option by the copyright holder. - - - - - - - - This is the XML Schema for the JSP 2.2 deployment descriptor - types. The JSP 2.2 schema contains all the special - structures and datatypes that are necessary to use JSP files - from a web application. - - The contents of this schema is used by the web-common_3_0.xsd - file to define JSP specific content. - - - - - - - - The following conventions apply to all Java EE - deployment descriptor elements unless indicated otherwise. - - - In elements that specify a pathname to a file within the - same JAR file, relative filenames (i.e., those not - starting with "/") are considered relative to the root of - the JAR file's namespace. Absolute filenames (i.e., those - starting with "/") also specify names in the root of the - JAR file's namespace. In general, relative names are - preferred. The exception is .war files where absolute - names are preferred for consistency with the Servlet API. - - - - - - - - - - - - - - The jsp-configType is used to provide global configuration - information for the JSP files in a web application. It has - two subelements, taglib and jsp-property-group. - - - - - - - - - - - - - - - - - - The jsp-file element contains the full path to a JSP file - within the web application beginning with a `/'. - - - - - - - - - - - - - - - - The jsp-property-groupType is used to group a number of - files so they can be given global property information. - All files so described are deemed to be JSP files. The - following additional properties can be described: - - - Control whether EL is ignored. - - Control whether scripting elements are invalid. - - Indicate pageEncoding information. - - Indicate that a resource is a JSP document (XML). - - Prelude and Coda automatic includes. - - Control whether the character sequence #{ is allowed - when used as a String literal. - - Control whether template text containing only - whitespaces must be removed from the response output. - - Indicate the default contentType information. - - Indicate the default buffering model for JspWriter - - Control whether error should be raised for the use of - undeclared namespaces in a JSP page. - - - - - - - - - - - Can be used to easily set the isELIgnored - property of a group of JSP pages. By default, the - EL evaluation is enabled for Web Applications using - a Servlet 2.4 or greater web.xml, and disabled - otherwise. - - - - - - - - - The valid values of page-encoding are those of the - pageEncoding page directive. It is a - translation-time error to name different encodings - in the pageEncoding attribute of the page directive - of a JSP page and in a JSP configuration element - matching the page. It is also a translation-time - error to name different encodings in the prolog - or text declaration of a document in XML syntax and - in a JSP configuration element matching the document. - It is legal to name the same encoding through - mulitple mechanisms. - - - - - - - - - Can be used to easily disable scripting in a - group of JSP pages. By default, scripting is - enabled. - - - - - - - - - If true, denotes that the group of resources - that match the URL pattern are JSP documents, - and thus must be interpreted as XML documents. - If false, the resources are assumed to not - be JSP documents, unless there is another - property group that indicates otherwise. - - - - - - - - - The include-prelude element is a context-relative - path that must correspond to an element in the - Web Application. When the element is present, - the given path will be automatically included (as - in an include directive) at the beginning of each - JSP page in this jsp-property-group. - - - - - - - - - The include-coda element is a context-relative - path that must correspond to an element in the - Web Application. When the element is present, - the given path will be automatically included (as - in an include directive) at the end of each - JSP page in this jsp-property-group. - - - - - - - - - The character sequence #{ is reserved for EL expressions. - Consequently, a translation error occurs if the #{ - character sequence is used as a String literal, unless - this element is enabled (true). Disabled (false) by - default. - - - - - - - - - Indicates that template text containing only whitespaces - must be removed from the response output. It has no - effect on JSP documents (XML syntax). Disabled (false) - by default. - - - - - - - - - The valid values of default-content-type are those of the - contentType page directive. It specifies the default - response contentType if the page directive does not include - a contentType attribute. - - - - - - - - - The valid values of buffer are those of the - buffer page directive. It specifies if buffering should be - used for the output to response, and if so, the size of the - buffer to use. - - - - - - - - - The default behavior when a tag with unknown namespace is used - in a JSP page (regular syntax) is to silently ignore it. If - set to true, then an error must be raised during the translation - time when an undeclared tag is used in a JSP page. Disabled - (false) by default. - - - - - - - - - - - - - - - - The taglibType defines the syntax for declaring in - the deployment descriptor that a tag library is - available to the application. This can be done - to override implicit map entries from TLD files and - from the container. - - - - - - - - - A taglib-uri element describes a URI identifying a - tag library used in the web application. The body - of the taglib-uri element may be either an - absolute URI specification, or a relative URI. - There should be no entries in web.xml with the - same taglib-uri value. - - - - - - - - - the taglib-location element contains the location - (as a resource relative to the root of the web - application) where to find the Tag Library - Description file for the tag library. - - - - - - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-app_3_0.xsd b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-app_3_0.xsd deleted file mode 100644 index bbcdf43cd3a..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-app_3_0.xsd +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - - Copyright 2003-2009 Sun Microsystems, Inc. All rights reserved. - - The contents of this file are subject to the terms of either the - GNU General Public License Version 2 only ("GPL") or the Common - Development and Distribution License("CDDL") (collectively, the - "License"). You may not use this file except in compliance with - the License. You can obtain a copy of the License at - https://glassfish.dev.java.net/public/CDDL+GPL.html or - glassfish/bootstrap/legal/LICENSE.txt. See the License for the - specific language governing permissions and limitations under the - License. - - When distributing the software, include this License Header - Notice in each file and include the License file at - glassfish/bootstrap/legal/LICENSE.txt. Sun designates this - particular file as subject to the "Classpath" exception as - provided by Sun in the GPL Version 2 section of the License file - that accompanied this code. If applicable, add the following - below the License Header, with the fields enclosed by brackets [] - replaced by your own identifying information: - "Portions Copyrighted [year] [name of copyright owner]" - - Contributor(s): - - If you wish your version of this file to be governed by only the - CDDL or only the GPL Version 2, indicate your decision by adding - "[Contributor] elects to include this software in this - distribution under the [CDDL or GPL Version 2] license." If you - don't indicate a single choice of license, a recipient has the - option to distribute your version of this file under either the - CDDL, the GPL Version 2 or to extend the choice of license to its - licensees as provided above. However, if you add GPL Version 2 - code and therefore, elected the GPL Version 2 license, then the - option applies only if the new code is made subject to such - option by the copyright holder. - - - - - - - - ... - - - The instance documents may indicate the published version of - the schema using the xsi:schemaLocation attribute for Java EE - namespace with the following location: - - http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd - - ]]> - - - - - - - The following conventions apply to all Java EE - deployment descriptor elements unless indicated otherwise. - - - In elements that specify a pathname to a file within the - same JAR file, relative filenames (i.e., those not - starting with "/") are considered relative to the root of - the JAR file's namespace. Absolute filenames (i.e., those - starting with "/") also specify names in the root of the - JAR file's namespace. In general, relative names are - preferred. The exception is .war files where absolute - names are preferred for consistency with the Servlet API. - - - - - - - - - - - - - - The web-app element is the root of the deployment - descriptor for a web application. Note that the sub-elements - of this element can be in the arbitrary order. Because of - that, the multiplicity of the elements of distributable, - session-config, welcome-file-list, jsp-config, login-config, - and locale-encoding-mapping-list was changed from "?" to "*" - in this schema. However, the deployment descriptor instance - file must not contain multiple elements of session-config, - jsp-config, and login-config. When there are multiple elements of - welcome-file-list or locale-encoding-mapping-list, the container - must concatenate the element contents. The multiple occurence - of the element distributable is redundant and the container - treats that case exactly in the same way when there is only - one distributable. - - - - - - - - The servlet element contains the name of a servlet. - The name must be unique within the web application. - - - - - - - - - - - The filter element contains the name of a filter. - The name must be unique within the web application. - - - - - - - - - - - The ejb-local-ref-name element contains the name of an EJB - reference. The EJB reference is an entry in the web - application's environment and is relative to the - java:comp/env context. The name must be unique within - the web application. - - It is recommended that name is prefixed with "ejb/". - - - - - - - - - - - The ejb-ref-name element contains the name of an EJB - reference. The EJB reference is an entry in the web - application's environment and is relative to the - java:comp/env context. The name must be unique within - the web application. - - It is recommended that name is prefixed with "ejb/". - - - - - - - - - - - The resource-env-ref-name element specifies the name of - a resource environment reference; its value is the - environment entry name used in the web application code. - The name is a JNDI name relative to the java:comp/env - context and must be unique within a web application. - - - - - - - - - - - The message-destination-ref-name element specifies the name of - a message destination reference; its value is the - environment entry name used in the web application code. - The name is a JNDI name relative to the java:comp/env - context and must be unique within a web application. - - - - - - - - - - - The res-ref-name element specifies the name of a - resource manager connection factory reference. The name - is a JNDI name relative to the java:comp/env context. - The name must be unique within a web application. - - - - - - - - - - - The env-entry-name element contains the name of a web - application's environment entry. The name is a JNDI - name relative to the java:comp/env context. The name - must be unique within a web application. - - - - - - - - - - - A role-name-key is specified to allow the references - from the security-role-refs. - - - - - - - - - - - The keyref indicates the references from - security-role-ref to a specified role-name. - - - - - - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-common_3_0.xsd b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-common_3_0.xsd deleted file mode 100644 index f994bc2c651..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/web-common_3_0.xsd +++ /dev/null @@ -1,1575 +0,0 @@ - - - - - - DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - - Copyright 2003-2009 Sun Microsystems, Inc. All rights reserved. - - The contents of this file are subject to the terms of either the - GNU General Public License Version 2 only ("GPL") or the Common - Development and Distribution License("CDDL") (collectively, the - "License"). You may not use this file except in compliance with - the License. You can obtain a copy of the License at - https://glassfish.dev.java.net/public/CDDL+GPL.html or - glassfish/bootstrap/legal/LICENSE.txt. See the License for the - specific language governing permissions and limitations under the - License. - - When distributing the software, include this License Header - Notice in each file and include the License file at - glassfish/bootstrap/legal/LICENSE.txt. Sun designates this - particular file as subject to the "Classpath" exception as - provided by Sun in the GPL Version 2 section of the License file - that accompanied this code. If applicable, add the following - below the License Header, with the fields enclosed by brackets [] - replaced by your own identifying information: - "Portions Copyrighted [year] [name of copyright owner]" - - Contributor(s): - - If you wish your version of this file to be governed by only the - CDDL or only the GPL Version 2, indicate your decision by adding - "[Contributor] elects to include this software in this - distribution under the [CDDL or GPL Version 2] license." If you - don't indicate a single choice of license, a recipient has the - option to distribute your version of this file under either the - CDDL, the GPL Version 2 or to extend the choice of license to its - licensees as provided above. However, if you add GPL Version 2 - code and therefore, elected the GPL Version 2 license, then the - option applies only if the new code is made subject to such - option by the copyright holder. - - - - - - - - ... - - - The instance documents may indicate the published version of - the schema using the xsi:schemaLocation attribute for Java EE - namespace with the following location: - - http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd - - ]]> - - - - - - - The following conventions apply to all Java EE - deployment descriptor elements unless indicated otherwise. - - - In elements that specify a pathname to a file within the - same JAR file, relative filenames (i.e., those not - starting with "/") are considered relative to the root of - the JAR file's namespace. Absolute filenames (i.e., those - starting with "/") also specify names in the root of the - JAR file's namespace. In general, relative names are - preferred. The exception is .war files where absolute - names are preferred for consistency with the Servlet API. - - - - - - - - - - - - - - - - - The context-param element contains the declaration - of a web application's servlet context - initialization parameters. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The metadata-complete attribute defines whether this - deployment descriptor and other related deployment - descriptors for this module (e.g., web service - descriptors) are complete, or whether the class - files available to this module and packaged with - this application should be examined for annotations - that specify deployment information. - - If metadata-complete is set to "true", the deployment - tool must ignore any annotations that specify deployment - information, which might be present in the class files - of the application. - - If metadata-complete is not specified or is set to - "false", the deployment tool must examine the class - files of the application for annotations, as - specified by the specifications. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The auth-constraintType indicates the user roles that - should be permitted access to this resource - collection. The role-name used here must either correspond - to the role-name of one of the security-role elements - defined for this web application, or be the specially - reserved role-name "*" that is a compact syntax for - indicating all roles in the web application. If both "*" - and rolenames appear, the container interprets this as all - roles. If no roles are defined, no user is allowed access - to the portion of the web application described by the - containing security-constraint. The container matches - role names case sensitively when determining access. - - - - - - - - - - - - - - - - - - The auth-methodType is used to configure the authentication - mechanism for the web application. As a prerequisite to - gaining access to any web resources which are protected by - an authorization constraint, a user must have authenticated - using the configured mechanism. Legal values are "BASIC", - "DIGEST", "FORM", "CLIENT-CERT", or a vendor-specific - authentication scheme. - - Used in: login-config - - - - - - - - - - - - - - - - The dispatcher has five legal values: FORWARD, REQUEST, - INCLUDE, ASYNC, and ERROR. - - A value of FORWARD means the Filter will be applied under - RequestDispatcher.forward() calls. - A value of REQUEST means the Filter will be applied under - ordinary client calls to the path or servlet. - A value of INCLUDE means the Filter will be applied under - RequestDispatcher.include() calls. - A value of ASYNC means the Filter will be applied under - calls dispatched from an AsyncContext. - A value of ERROR means the Filter will be applied under the - error page mechanism. - - The absence of any dispatcher elements in a filter-mapping - indicates a default of applying filters only under ordinary - client calls to the path or servlet. - - - - - - - - - - - - - - - - - - - - - - The error-code contains an HTTP error code, ex: 404 - - Used in: error-page - - - - - - - - - - - - - - - - - - - The error-pageType contains a mapping between an error code - or exception type to the path of a resource in the web - application. - - Error-page declarations using the exception-type element in - the deployment descriptor must be unique up to the class name of - the exception-type. Similarly, error-page declarations using the - status-code element must be unique in the deployment descriptor - up to the status code. - - Used in: web-app - - - - - - - - - - - The exception-type contains a fully qualified class - name of a Java exception type. - - - - - - - - - - The location element contains the location of the - resource in the web application relative to the root of - the web application. The value of the location must have - a leading `/'. - - - - - - - - - - - - - - - - The filterType is used to declare a filter in the web - application. The filter is mapped to either a servlet or a - URL pattern in the filter-mapping element, using the - filter-name value to reference. Filters can access the - initialization parameters declared in the deployment - descriptor at runtime via the FilterConfig interface. - - Used in: web-app - - - - - - - - - - - The fully qualified classname of the filter. - - - - - - - - - - The init-param element contains a name/value pair as - an initialization param of a servlet filter - - - - - - - - - - - - - - - - Declaration of the filter mappings in this web - application is done by using filter-mappingType. - The container uses the filter-mapping - declarations to decide which filters to apply to a request, - and in what order. The container matches the request URI to - a Servlet in the normal way. To determine which filters to - apply it matches filter-mapping declarations either on - servlet-name, or on url-pattern for each filter-mapping - element, depending on which style is used. The order in - which filters are invoked is the order in which - filter-mapping declarations that match a request URI for a - servlet appear in the list of filter-mapping elements.The - filter-name value must be the value of the filter-name - sub-elements of one of the filter declarations in the - deployment descriptor. - - - - - - - - - - - - - - - - - - - - - - This type defines a string which contains at least one - character. - - - - - - - - - - - - - - - - - - The logical name of the filter is declare - by using filter-nameType. This name is used to map the - filter. Each filter name is unique within the web - application. - - Used in: filter, filter-mapping - - - - - - - - - - - - - - - - The form-login-configType specifies the login and error - pages that should be used in form based login. If form based - authentication is not used, these elements are ignored. - - Used in: login-config - - - - - - - - - The form-login-page element defines the location in the web - app where the page that can be used for login can be - found. The path begins with a leading / and is interpreted - relative to the root of the WAR. - - - - - - - - - The form-error-page element defines the location in - the web app where the error page that is displayed - when login is not successful can be found. - The path begins with a leading / and is interpreted - relative to the root of the WAR. - - - - - - - - - - - - - A HTTP method type as defined in HTTP 1.1 section 2.2. - - - - - - - - - - - - - - - - - - - - - - - - - - The login-configType is used to configure the authentication - method that should be used, the realm name that should be - used for this application, and the attributes that are - needed by the form login mechanism. - - Used in: web-app - - - - - - - - - - The realm name element specifies the realm name to - use in HTTP Basic authorization. - - - - - - - - - - - - - - - - - The mime-mappingType defines a mapping between an extension - and a mime type. - - Used in: web-app - - - - - - - - The extension element contains a string describing an - extension. example: "txt" - - - - - - - - - - - - - - - - - The mime-typeType is used to indicate a defined mime type. - - Example: - "text/plain" - - Used in: mime-mapping - - - - - - - - - - - - - - - - - - The security-constraintType is used to associate - security constraints with one or more web resource - collections - - Used in: web-app - - - - - - - - - - - - - - - - - - - - The servletType is used to declare a servlet. - It contains the declarative data of a - servlet. If a jsp-file is specified and the load-on-startup - element is present, then the JSP should be precompiled and - loaded. - - Used in: web-app - - - - - - - - - - - - The servlet-class element contains the fully - qualified class name of the servlet. - - - - - - - - - - - - The load-on-startup element indicates that this - servlet should be loaded (instantiated and have - its init() called) on the startup of the web - application. The optional contents of these - element must be an integer indicating the order in - which the servlet should be loaded. If the value - is a negative integer, or the element is not - present, the container is free to load the servlet - whenever it chooses. If the value is a positive - integer or 0, the container must load and - initialize the servlet as the application is - deployed. The container must guarantee that - servlets marked with lower integers are loaded - before servlets marked with higher integers. The - container may choose the order of loading of - servlets with the same load-on-start-up value. - - - - - - - - - - - - - - - - - - - - - The servlet-mappingType defines a mapping between a - servlet and a url pattern. - - Used in: web-app - - - - - - - - - - - - - - - - - - The servlet-name element contains the canonical name of the - servlet. Each servlet name is unique within the web - application. - - - - - - - - - - - - - - - - The session-configType defines the session parameters - for this web application. - - Used in: web-app - - - - - - - - - The session-timeout element defines the default - session timeout interval for all sessions created - in this web application. The specified timeout - must be expressed in a whole number of minutes. - If the timeout is 0 or less, the container ensures - the default behaviour of sessions is never to time - out. If this element is not specified, the container - must set its default timeout period. - - - - - - - - - The cookie-config element defines the configuration of the - session tracking cookies created by this web application. - - - - - - - - - The tracking-mode element defines the tracking modes - for sessions created by this web application - - - - - - - - - - - - - - - - The cookie-configType defines the configuration for the - session tracking cookies of this web application. - - Used in: session-config - - - - - - - - - The name that will be assigned to any session tracking - cookies created by this web application. - The default is JSESSIONID - - - - - - - - - The domain name that will be assigned to any session tracking - cookies created by this web application. - - - - - - - - - The path that will be assigned to any session tracking - cookies created by this web application. - - - - - - - - - The comment that will be assigned to any session tracking - cookies created by this web application. - - - - - - - - - Specifies whether any session tracking cookies created - by this web application will be marked as HttpOnly - - - - - - - - - Specifies whether any session tracking cookies created - by this web application will be marked as secure - even if the request that initiated the corresponding session - is using plain HTTP instead of HTTPS - - - - - - - - - The lifetime (in seconds) that will be assigned to any - session tracking cookies created by this web application. - Default is -1 - - - - - - - - - - - - - - - - The name that will be assigned to any session tracking - cookies created by this web application. - The default is JSESSIONID - - Used in: cookie-config - - - - - - - - - - - - - - - - The domain name that will be assigned to any session tracking - cookies created by this web application. - - Used in: cookie-config - - - - - - - - - - - - - - - - The path that will be assigned to any session tracking - cookies created by this web application. - - Used in: cookie-config - - - - - - - - - - - - - - - - The comment that will be assigned to any session tracking - cookies created by this web application. - - Used in: cookie-config - - - - - - - - - - - - - - - - The tracking modes for sessions created by this web - application - - Used in: session-config - - - - - - - - - - - - - - - - - - - - The transport-guaranteeType specifies that the communication - between client and server should be NONE, INTEGRAL, or - CONFIDENTIAL. NONE means that the application does not - require any transport guarantees. A value of INTEGRAL means - that the application requires that the data sent between the - client and server be sent in such a way that it can't be - changed in transit. CONFIDENTIAL means that the application - requires that the data be transmitted in a fashion that - prevents other entities from observing the contents of the - transmission. In most cases, the presence of the INTEGRAL or - CONFIDENTIAL flag will indicate that the use of SSL is - required. - - Used in: user-data-constraint - - - - - - - - - - - - - - - - - - - - The user-data-constraintType is used to indicate how - data communicated between the client and container should be - protected. - - Used in: security-constraint - - - - - - - - - - - - - - - - - - The elements that use this type designate a path starting - with a "/" and interpreted relative to the root of a WAR - file. - - - - - - - - - - - - - - - This type contains the recognized versions of - web-application supported. It is used to designate the - version of the web application. - - - - - - - - - - - - - - - - The web-resource-collectionType is used to identify the - resources and HTTP methods on those resources to which a - security constraint applies. If no HTTP methods are specified, - then the security constraint applies to all HTTP methods. - If HTTP methods are specified by http-method-omission - elements, the security constraint applies to all methods - except those identified in the collection. - http-method-omission and http-method elements are never - mixed in the same collection. - - Used in: security-constraint - - - - - - - - - The web-resource-name contains the name of this web - resource collection. - - - - - - - - - - - - Each http-method names an HTTP method to which the - constraint applies. - - - - - - - - - Each http-method-omission names an HTTP method to - which the constraint does not apply. - - - - - - - - - - - - - - - - - The welcome-file-list contains an ordered list of welcome - files elements. - - Used in: web-app - - - - - - - - - The welcome-file element contains file name to use - as a default welcome file, such as index.html - - - - - - - - - - - - - The localeType defines valid locale defined by ISO-639-1 - and ISO-3166. - - - - - - - - - - - - - The encodingType defines IANA character sets. - - - - - - - - - - - - - - - - The locale-encoding-mapping-list contains one or more - locale-encoding-mapping(s). - - - - - - - - - - - - - - - - - The locale-encoding-mapping contains locale name and - encoding name. The locale name must be either "Language-code", - such as "ja", defined by ISO-639 or "Language-code_Country-code", - such as "ja_JP". "Country code" is defined by ISO-3166. - - - - - - - - - - - - - - - - - - This element indicates that the ordering sub-element in which - it was placed should take special action regarding the ordering - of this application resource relative to other application - configuration resources. - See section 8.2.2 of the specification for details. - - - - - - - - - - - - - - Please see section 8.2.2 of the specification for details. - - - - - - - - - - - - - - - - - Please see section 8.2.2 of the specification for details. - - - - - - - - - - - - - - - - - This element contains a sequence of "name" elements, each of - which - refers to an application configuration resource by the "name" - declared on its web.xml fragment. This element can also contain - a single "others" element which specifies that this document - comes - before or after other documents within the application. - See section 8.2.2 of the specification for details. - - - - - - - - - - - - - - - - - This element specifies configuration information related to the - handling of multipart/form-data requests. - - - - - - - - - The directory location where uploaded files will be stored - - - - - - - - - The maximum size limit of uploaded files - - - - - - - - - The maximum size limit of multipart/form-data requests - - - - - - - - - The size threshold after which an uploaded file will be - written to disk - - - - - - - - diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/xml.xsd b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/xml.xsd deleted file mode 100644 index aea7d0db0a4..00000000000 --- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/xsd/xml.xsd +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - -
        -

        About the XML namespace

        - -
        -

        - This schema document describes the XML namespace, in a form - suitable for import by other schema documents. -

        -

        - See - http://www.w3.org/XML/1998/namespace.html and - - http://www.w3.org/TR/REC-xml for information - about this namespace. -

        -

        - Note that local names in this namespace are intended to be - defined only by the World Wide Web Consortium or its subgroups. - The names currently defined in this namespace are listed below. - They should not be used with conflicting semantics by any Working - Group, specification, or document instance. -

        -

        - See further below in this document for more information about how to refer to this schema document from your own - XSD schema documents and about the - namespace-versioning policy governing this schema document. -

        -
        -
        -
        -
        - - - - -
        - -

        lang (as an attribute name)

        -

        - denotes an attribute whose value - is a language code for the natural language of the content of - any element; its value is inherited. This name is reserved - by virtue of its definition in the XML specification.

        - -
        -
        -

        Notes

        -

        - Attempting to install the relevant ISO 2- and 3-letter - codes as the enumerated possible values is probably never - going to be a realistic possibility. -

        -

        - See BCP 47 at - http://www.rfc-editor.org/rfc/bcp/bcp47.txt - and the IANA language subtag registry at - - http://www.iana.org/assignments/language-subtag-registry - for further information. -

        -

        - The union allows for the 'un-declaration' of xml:lang with - the empty string. -

        -
        -
        -
        - - - - - - - - - -
        - - - - -
        - -

        space (as an attribute name)

        -

        - denotes an attribute whose - value is a keyword indicating what whitespace processing - discipline is intended for the content of the element; its - value is inherited. This name is reserved by virtue of its - definition in the XML specification.

        - -
        -
        -
        - - - - - - -
        - - - -
        - -

        base (as an attribute name)

        -

        - denotes an attribute whose value - provides a URI to be used as the base for interpreting any - relative URIs in the scope of the element on which it - appears; its value is inherited. This name is reserved - by virtue of its definition in the XML Base specification.

        - -

        - See http://www.w3.org/TR/xmlbase/ - for information about this attribute. -

        -
        -
        -
        -
        - - - - -
        - -

        id (as an attribute name)

        -

        - denotes an attribute whose value - should be interpreted as if declared to be of type ID. - This name is reserved by virtue of its definition in the - xml:id specification.

        - -

        - See http://www.w3.org/TR/xml-id/ - for information about this attribute. -

        -
        -
        -
        -
        - - - - - - - - - - -
        - -

        Father (in any context at all)

        - -
        -

        - denotes Jon Bosak, the chair of - the original XML Working Group. This name is reserved by - the following decision of the W3C XML Plenary and - XML Coordination groups: -

        -
        -

        - In appreciation for his vision, leadership and - dedication the W3C XML Plenary on this 10th day of - February, 2000, reserves for Jon Bosak in perpetuity - the XML name "xml:Father". -

        -
        -
        -
        -
        -
        - - - -
        -

        About this schema document

        - -
        -

        - This schema defines attributes and an attribute group suitable - for use by schemas wishing to allow xml:base, - xml:lang, xml:space or - xml:id attributes on elements they define. -

        -

        - To enable this, such a schema must import this schema for - the XML namespace, e.g. as follows: -

        -
        -          <schema . . .>
        -           . . .
        -           <import namespace="http://www.w3.org/XML/1998/namespace"
        -                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
        -     
        -

        - or -

        -
        -           <import namespace="http://www.w3.org/XML/1998/namespace"
        -                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
        -     
        -

        - Subsequently, qualified reference to any of the attributes or the - group defined below will have the desired effect, e.g. -

        -
        -          <type . . .>
        -           . . .
        -           <attributeGroup ref="xml:specialAttrs"/>
        -     
        -

        - will define a type which will schema-validate an instance element - with any of those attributes. -

        -
        -
        -
        -
        - - - -
        -

        Versioning policy for this schema document

        -
        -

        - In keeping with the XML Schema WG's standard versioning - policy, this schema document will persist at - - http://www.w3.org/2009/01/xml.xsd. -

        -

        - At the date of issue it can also be found at - - http://www.w3.org/2001/xml.xsd. -

        -

        - The schema document at that URI may however change in the future, - in order to remain compatible with the latest version of XML - Schema itself, or with the XML namespace itself. In other words, - if the XML Schema or XML namespaces change, the version of this - document at - http://www.w3.org/2001/xml.xsd - - will change accordingly; the version at - - http://www.w3.org/2009/01/xml.xsd - - will not change. -

        -

        - Previous dated (and unchanging) versions of this schema - document are at: -

        - -
        -
        -
        -
        - -
        - diff --git a/hapi-fhir-jpaserver-example/src/test/java/ca/uhn/fhir/jpa/demo/ExampleServerIT.java b/hapi-fhir-jpaserver-example/src/test/java/ca/uhn/fhir/jpa/demo/ExampleServerIT.java deleted file mode 100644 index 210cb7a0650..00000000000 --- a/hapi-fhir-jpaserver-example/src/test/java/ca/uhn/fhir/jpa/demo/ExampleServerIT.java +++ /dev/null @@ -1,87 +0,0 @@ -package ca.uhn.fhir.jpa.demo; - -import static org.junit.Assert.assertEquals; - -import java.io.File; -import java.io.IOException; - -import ca.uhn.fhir.test.utilities.JettyUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; - -public class ExampleServerIT { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerIT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx = FhirContext.forDstu3(); - private static int ourPort; - - private static Server ourServer; - private static String ourServerBase; - - @Test - public void testCreateAndRead() throws IOException { - ourLog.info("Base URL is: http://localhost:" + ourPort + "/baseDstu3"); - String methodName = "testCreateResourceConditional"; - - Patient pt = new Patient(); - pt.addName().setFamily(methodName); - IIdType id = ourClient.create().resource(pt).execute().getId(); - - Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); - assertEquals(methodName, pt2.getName().get(0).getFamily()); - } - - @AfterClass - public static void afterClass() throws Exception { - JettyUtil.closeServer(ourServer); - } - - @BeforeClass - public static void beforeClass() throws Exception { - /* - * This runs under maven, and I'm not sure how else to figure out the target directory from code.. - */ - String path = ExampleServerIT.class.getClassLoader().getResource(".keep_hapi-fhir-jpaserver-example").getPath(); - path = new File(path).getParent(); - path = new File(path).getParent(); - path = new File(path).getParent(); - - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(ourPort); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-example"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - JettyUtil.startServer(ourServer); - ourPort = JettyUtil.getPortForStartedServer(ourServer); - - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - ourServerBase = "http://localhost:" + ourPort + "/baseDstu3"; - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); - - - } - - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } - - -} diff --git a/hapi-fhir-jpaserver-migrate/pom.xml b/hapi-fhir-jpaserver-migrate/pom.xml index 2820928227b..95acba688c5 100644 --- a/hapi-fhir-jpaserver-migrate/pom.xml +++ b/hapi-fhir-jpaserver-migrate/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -49,16 +49,16 @@ h2 test - + junit junit diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java index 4ae1f557868..1a73e3e542a 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java @@ -223,6 +223,8 @@ public class JdbcUtils { int dataType = indexes.getInt("DATA_TYPE"); Long length = indexes.getLong("COLUMN_SIZE"); switch (dataType) { + case Types.BOOLEAN: + return new ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN, length); case Types.VARCHAR: return new ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, length); case Types.NUMERIC: diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTask.java index 70aded99a0b..4797f1cfefc 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTask.java @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.Set; -public class AddColumnTask extends BaseTableColumnTypeTask { +public class AddColumnTask extends BaseTableColumnTypeTask { private static final Logger ourLog = LoggerFactory.getLogger(AddColumnTask.class); @@ -51,7 +51,7 @@ public class AddColumnTask extends BaseTableColumnTypeTask { String typeStatement = getTypeStatement(); - String sql = ""; + String sql; switch (getDriverType()) { case DERBY_EMBEDDED: case MARIADB_10_1: diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java index d087243e480..7cbe9832a34 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTask.java @@ -32,7 +32,7 @@ import java.util.Set; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class AddForeignKeyTask extends BaseTableColumnTask { +public class AddForeignKeyTask extends BaseTableColumnTask { private static final Logger ourLog = LoggerFactory.getLogger(AddForeignKeyTask.class); private String myConstraintName; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTask.java index b32fa3d3e1a..bc148c50cbe 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTask.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class AddIdGeneratorTask extends BaseTask { +public class AddIdGeneratorTask extends BaseTask { private static final Logger ourLog = LoggerFactory.getLogger(AddIdGeneratorTask.class); private final String myGeneratorName; @@ -103,7 +103,7 @@ public class AddIdGeneratorTask extends BaseTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { AddIdGeneratorTask otherObject = (AddIdGeneratorTask) theOtherObject; theBuilder.append(myGeneratorName, otherObject.myGeneratorName); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java index ad82b38000f..cd4b3305fb9 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java @@ -34,7 +34,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; -public class AddIndexTask extends BaseTableTask { +public class AddIndexTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(AddIndexTask.class); private String myIndexName; @@ -97,7 +97,7 @@ public class AddIndexTask extends BaseTableTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { super.generateEquals(theBuilder, theOtherObject); AddIndexTask otherObject = (AddIndexTask) theOtherObject; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTask.java index 25bdbda4458..c31267bd682 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTask.java @@ -31,7 +31,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class AddTableByColumnTask extends BaseTableTask { +public class AddTableByColumnTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(AddTableByColumnTask.class); @@ -110,7 +110,7 @@ public class AddTableByColumnTask extends BaseTableTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { super.generateEquals(theBuilder, theOtherObject); AddTableByColumnTask otherObject = (AddTableByColumnTask) theOtherObject; theBuilder.append(myAddColumnTasks, otherObject.myAddColumnTasks); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableRawSqlTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableRawSqlTask.java index 6c9e980c0cf..2cc9dbec6ef 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableRawSqlTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableRawSqlTask.java @@ -37,7 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class AddTableRawSqlTask extends BaseTableTask { +public class AddTableRawSqlTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(AddTableRawSqlTask.class); private Map> myDriverToSqls = new HashMap<>(); @@ -91,7 +91,7 @@ public class AddTableRawSqlTask extends BaseTableTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { super.generateEquals(theBuilder, theOtherObject); AddTableRawSqlTask otherObject = (AddTableRawSqlTask) theOtherObject; theBuilder.append(myDriverNeutralSqls, otherObject.myDriverNeutralSqls); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTask.java index 18763523739..056f861fd64 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTask.java @@ -37,7 +37,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; -public class ArbitrarySqlTask extends BaseTask { +public class ArbitrarySqlTask extends BaseTask { private static final Logger ourLog = LoggerFactory.getLogger(ArbitrarySqlTask.class); private final String myDescription; @@ -104,7 +104,7 @@ public class ArbitrarySqlTask extends BaseTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { ArbitrarySqlTask otherObject = (ArbitrarySqlTask) theOtherObject; theBuilder.append(myTableName, otherObject.myTableName); } @@ -124,12 +124,10 @@ public class ArbitrarySqlTask extends BaseTask { private class QueryTask extends Task { private final String mySql; - private final QueryModeEnum myMode; private final Consumer> myConsumer; public QueryTask(String theSql, QueryModeEnum theMode, Consumer> theConsumer) { mySql = theSql; - myMode = theMode; myConsumer = theConsumer; setDescription("Execute raw sql"); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java index dc38d081ad4..567ebf7e052 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java @@ -27,7 +27,7 @@ import org.thymeleaf.util.StringUtils; import java.util.Locale; -public abstract class BaseTableColumnTask> extends BaseTableTask { +public abstract class BaseTableColumnTask extends BaseTableTask { private String myColumnName; //If a concrete class decides to, they can define a custom WHERE clause for the task. @@ -37,10 +37,9 @@ public abstract class BaseTableColumnTask> extends Ba super(theProductVersion, theSchemaVersion); } - @SuppressWarnings("unchecked") - public T setColumnName(String theColumnName) { + public BaseTableColumnTask setColumnName(String theColumnName) { myColumnName = StringUtils.toUpperCase(theColumnName, Locale.US); - return (T) this; + return this; } public String getColumnName() { @@ -65,8 +64,8 @@ public abstract class BaseTableColumnTask> extends Ba } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { - BaseTableColumnTask otherObject = (BaseTableColumnTask) theOtherObject; + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + BaseTableColumnTask otherObject = (BaseTableColumnTask) theOtherObject; super.generateEquals(theBuilder, otherObject); theBuilder.append(myColumnName, otherObject.myColumnName); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java index 49fee53525d..f7450079f31 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java @@ -30,7 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -public abstract class BaseTableColumnTypeTask> extends BaseTableColumnTask { +public abstract class BaseTableColumnTypeTask extends BaseTableColumnTask { private ColumnTypeEnum myColumnType; private Map> myColumnTypeToDriverTypeToSqlType = new HashMap<>(); private Boolean myNullable; @@ -82,6 +82,14 @@ public abstract class BaseTableColumnTypeTask setColumnLength(long theColumnLength) { + public BaseTableColumnTypeTask setColumnLength(long theColumnLength) { myColumnLength = theColumnLength; return this; } @@ -184,7 +191,7 @@ public abstract class BaseTableColumnTypeTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { BaseTableColumnTypeTask otherObject = (BaseTableColumnTypeTask) theOtherObject; super.generateEquals(theBuilder, otherObject); theBuilder.append(getColumnTypeName(myColumnType), getColumnTypeName(otherObject.myColumnType)); @@ -204,6 +211,7 @@ public abstract class BaseTableColumnTypeTask> extends BaseTask { +public abstract class BaseTableTask extends BaseTask { private String myTableName; @@ -36,11 +36,11 @@ public abstract class BaseTableTask> extends BaseTask public String getTableName() { return myTableName; } - public T setTableName(String theTableName) { + + public BaseTableTask setTableName(String theTableName) { Validate.notBlank(theTableName); myTableName = theTableName; - //noinspection unchecked - return (T) this; + return this; } @Override @@ -49,8 +49,8 @@ public abstract class BaseTableTask> extends BaseTask } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { - BaseTableTask otherObject = (BaseTableTask) theOtherObject; + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + BaseTableTask otherObject = (BaseTableTask) theOtherObject; theBuilder.append(myTableName, otherObject.myTableName); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java index c4f061caa3b..39b666a8e61 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java @@ -38,7 +38,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public abstract class BaseTask { +public abstract class BaseTask { public static final String MIGRATION_VERSION_PATTERN = "\\d{8}\\.\\d+"; private static final Logger ourLog = LoggerFactory.getLogger(BaseTask.class); @@ -84,9 +84,9 @@ public abstract class BaseTask { } @SuppressWarnings("unchecked") - public T setDescription(String theDescription) { + public BaseTask setDescription(String theDescription) { myDescription = theDescription; - return (T) this; + return this; } public List getExecutedStatements() { @@ -173,6 +173,11 @@ public abstract class BaseTask { myFailureAllowed = theFailureAllowed; } + protected boolean isFailureAllowed() { + return myFailureAllowed; + } + + public String getFlywayVersion() { String releasePart = myProductVersion; if (releasePart.startsWith("V")) { @@ -196,7 +201,7 @@ public abstract class BaseTask { return myDoNothing; } - public BaseTask setDoNothing(boolean theDoNothing) { + public BaseTask setDoNothing(boolean theDoNothing) { myDoNothing = theDoNothing; return this; } @@ -216,14 +221,14 @@ public abstract class BaseTask { return false; } @SuppressWarnings("unchecked") - T otherObject = (T) theObject; + BaseTask otherObject = (BaseTask) theObject; EqualsBuilder b = new EqualsBuilder(); generateEquals(b, otherObject); return b.isEquals(); } - protected abstract void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject); + protected abstract void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject); public static class ExecutedStatement { private final String mySql; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTask.java index f85c2a5f5e9..ff1acc30f15 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTask.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.Set; -public class DropColumnTask extends BaseTableColumnTask { +public class DropColumnTask extends BaseTableColumnTask { private static final Logger ourLog = LoggerFactory.getLogger(DropColumnTask.class); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTask.java index 221bbd602b9..33a78d4c29a 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTask.java @@ -36,7 +36,7 @@ import java.util.Set; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class DropForeignKeyTask extends BaseTableTask { +public class DropForeignKeyTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(DropForeignKeyTask.class); private String myConstraintName; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTask.java index 344571c2554..04a68915e81 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTask.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class DropIdGeneratorTask extends BaseTask { +public class DropIdGeneratorTask extends BaseTask { private static final Logger ourLog = LoggerFactory.getLogger(DropIdGeneratorTask.class); private final String myGeneratorName; @@ -69,6 +69,8 @@ public class DropIdGeneratorTask extends BaseTask { } break; case DERBY_EMBEDDED: + sql = "drop sequence " + myGeneratorName + " restrict"; + break; case H2_EMBEDDED: sql = "drop sequence " + myGeneratorName; break; @@ -103,7 +105,7 @@ public class DropIdGeneratorTask extends BaseTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { DropIdGeneratorTask otherObject = (DropIdGeneratorTask) theOtherObject; theBuilder.append(myGeneratorName, otherObject.myGeneratorName); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java index f07f0a95de2..3d6d20823d1 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTask.java @@ -35,7 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; -public class DropIndexTask extends BaseTableTask { +public class DropIndexTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(DropIndexTask.class); private String myIndexName; @@ -111,9 +111,11 @@ public class DropIndexTask extends BaseTableTask { sql.add("alter table " + theTableName + " drop index " + theIndexName); break; case H2_EMBEDDED: - case DERBY_EMBEDDED: sql.add("drop index " + theIndexName); break; + case DERBY_EMBEDDED: + sql.add("alter table " + theTableName + " drop constraint " + theIndexName); + break; case POSTGRES_9_4: sql.add("alter table " + theTableName + " drop constraint if exists " + theIndexName + " cascade"); sql.add("drop index if exists " + theIndexName + " cascade"); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTask.java index ca6fc1ab8c1..3d9eb3b4fcf 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTask.java @@ -29,7 +29,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; -public class DropTableTask extends BaseTableTask { +public class DropTableTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(DropTableTask.class); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTask.java index 00ba0650836..1a10bb95729 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTask.java @@ -33,7 +33,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ExecuteRawSqlTask extends BaseTask { +public class ExecuteRawSqlTask extends BaseTask { private static final Logger ourLog = LoggerFactory.getLogger(ExecuteRawSqlTask.class); private Map> myDriverToSqls = new HashMap<>(); @@ -80,7 +80,7 @@ public class ExecuteRawSqlTask extends BaseTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { ExecuteRawSqlTask otherObject = (ExecuteRawSqlTask) theOtherObject; theBuilder.append(myDriverNeutralSqls, otherObject.myDriverNeutralSqls); theBuilder.append(myDriverToSqls, otherObject.myDriverToSqls); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java index e7c0481f56c..3a2f28c6474 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTask.java @@ -32,7 +32,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; -public class InitializeSchemaTask extends BaseTask { +public class InitializeSchemaTask extends BaseTask { private static final Logger ourLog = LoggerFactory.getLogger(InitializeSchemaTask.class); private final ISchemaInitializationProvider mySchemaInitializationProvider; @@ -71,7 +71,7 @@ public class InitializeSchemaTask extends BaseTask { } @Override - protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { + protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { InitializeSchemaTask otherObject = (InitializeSchemaTask) theOtherObject; theBuilder.append(mySchemaInitializationProvider, otherObject.mySchemaInitializationProvider); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java index f6ed13fbc9d..e21d008bccf 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.Set; -public class ModifyColumnTask extends BaseTableColumnTypeTask { +public class ModifyColumnTask extends BaseTableColumnTypeTask { private static final Logger ourLog = LoggerFactory.getLogger(ModifyColumnTask.class); @@ -62,10 +62,17 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask } Long taskColumnLength = getColumnLength(); - if (taskColumnLength != null && isNoColumnShrink()) { + boolean isShrinkOnly = false; + if (taskColumnLength != null) { long existingLength = existingType.getLength() != null ? existingType.getLength() : 0; if (existingLength > taskColumnLength) { - taskColumnLength = existingLength; + if (isNoColumnShrink()) { + taskColumnLength = existingLength; + } else { + if (existingType.getColumnTypeEnum() == getColumnType()) { + isShrinkOnly = true; + } + } } } @@ -129,6 +136,10 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask throw new IllegalStateException("Dont know how to handle " + getDriverType()); } + if (!isFailureAllowed() && isShrinkOnly) { + setFailureAllowed(true); + } + logInfo(ourLog, "Updating column {} on table {} to type {}", getColumnName(), getTableName(), type); if (sql != null) { executeSql(getTableName(), sql); @@ -139,4 +150,5 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask executeSql(getTableName(), sqlNotNull); } } + } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTask.java index edb003a299f..22ae7c2bcc6 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTask.java @@ -31,7 +31,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; import java.util.Set; -public class RenameColumnTask extends BaseTableTask { +public class RenameColumnTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(RenameColumnTask.class); private String myOldName; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameIndexTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameIndexTask.java index 7e394ff64e5..630733ef3f2 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameIndexTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameIndexTask.java @@ -35,7 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; -public class RenameIndexTask extends BaseTableTask { +public class RenameIndexTask extends BaseTableTask { private static final Logger ourLog = LoggerFactory.getLogger(RenameIndexTask.class); private String myOldIndexName; private String myNewIndexName; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 4e58767526d..7b8fdb0c097 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -21,13 +21,21 @@ package ca.uhn.fhir.jpa.migrate.tasks; */ import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; -import ca.uhn.fhir.jpa.migrate.taskdef.*; +import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask; +import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask; +import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask; +import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask; +import ca.uhn.fhir.jpa.migrate.taskdef.CalculateOrdinalDatesTask; import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks; import ca.uhn.fhir.jpa.migrate.tasks.api.Builder; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.util.VersionEnum; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; @SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection"}) @@ -51,10 +59,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { init400(); // 20190401 - 20190814 init410(); // 20190815 - 20191014 init420(); // 20191015 - 20200217 - init430(); // 20200218 - present + init500(); // 20200218 - present } - protected void init430() { // 20200218 - present + protected void init500() { // 20200218 - present Builder version = forVersion(VersionEnum.V4_3_0); // Eliminate circular dependency. @@ -75,6 +83,63 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { .addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_HIGH"))) ); // + + // Drop unused column + version.onTable("HFJ_RESOURCE").dropIndex("20200419.1", "IDX_RES_PROFILE"); + version.onTable("HFJ_RESOURCE").dropColumn("20200419.2", "RES_PROFILE").failureAllowed(); + + // Add Partitioning + Builder.BuilderAddTableByColumns partition = version.addTableByColumns("20200420.0", "HFJ_PARTITION", "PART_ID"); + partition.addColumn("PART_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + partition.addColumn("PART_NAME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200); + partition.addColumn("PART_DESC").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200); + partition.addIndex("20200420.1", "IDX_PART_NAME").unique(true).withColumns("PART_NAME"); + + // Partition columns on individual tables + version.onTable("HFJ_RESOURCE").addColumn("20200420.2", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RESOURCE").addColumn("20200420.3", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_RES_VER").addColumn("20200420.4", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RES_VER").addColumn("20200420.5", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ").addColumn("20200420.6", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ").addColumn("20200420.7", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ").addColumn("20200420.8", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_IDX_CMP_STRING_UNIQ").addColumn("20200420.9", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_HISTORY_TAG").addColumn("20200420.10", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_HISTORY_TAG").addColumn("20200420.11", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_RES_TAG").addColumn("20200420.12", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RES_TAG").addColumn("20200420.13", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_FORCED_ID").addColumn("20200420.14", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_FORCED_ID").addColumn("20200420.15", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_RES_LINK").addColumn("20200420.16", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RES_LINK").addColumn("20200420.17", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_STRING").addColumn("20200420.18", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_STRING").addColumn("20200420.19", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_COORDS").addColumn("20200420.20", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_COORDS").addColumn("20200420.21", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_NUMBER").addColumn("20200420.22", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_NUMBER").addColumn("20200420.23", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_TOKEN").addColumn("20200420.24", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_TOKEN").addColumn("20200420.25", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_DATE").addColumn("20200420.26", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_DATE").addColumn("20200420.27", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_URI").addColumn("20200420.28", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_URI").addColumn("20200420.29", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_SPIDX_QUANTITY").addColumn("20200420.30", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_SPIDX_QUANTITY").addColumn("20200420.31", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_RES_VER_PROV").addColumn("20200420.32", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RES_VER_PROV").addColumn("20200420.33", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + version.onTable("HFJ_RES_PARAM_PRESENT").addColumn("20200420.34", "PARTITION_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + version.onTable("HFJ_RES_PARAM_PRESENT").addColumn("20200420.35", "PARTITION_DATE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_ONLY); + + version.onTable("HFJ_SPIDX_STRING").modifyColumn("20200420.36", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_COORDS").modifyColumn("20200420.37", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_NUMBER").modifyColumn("20200420.38", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_TOKEN").modifyColumn("20200420.39", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_DATE").modifyColumn("20200420.40", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_URI").modifyColumn("20200420.41", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + version.onTable("HFJ_SPIDX_QUANTITY").modifyColumn("20200420.42", "SP_MISSING").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.BOOLEAN); + + } protected void init420() { // 20191015 - 20200217 @@ -349,10 +414,13 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { termValueSetConceptDesignationTable.addColumn("USE_CODE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 500); termValueSetConceptDesignationTable.addColumn("USE_DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 500); termValueSetConceptDesignationTable.addColumn("VAL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 500); + + // This index turned out not to be needed so it is disabled termValueSetConceptDesignationTable .addIndex("20190801.6", "IDX_VALUESET_C_DSGNTN_VAL") .unique(false) - .withColumns("VAL"); + .withColumns("VAL") + .doNothing(); // TermCodeSystemVersion version.startSectionWithMessage("Processing table: TRM_CODESYSTEM_VER"); @@ -474,7 +542,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxCoords .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.5") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) ); } @@ -497,7 +565,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxDate .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.10") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) ); } @@ -518,7 +586,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxNumber .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.14") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) ); } @@ -555,9 +623,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxQuantity .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.22") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) - .addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS"))) - .addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_IDENTITY_AND_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_UNITS"))) + .addCalculator("HASH_IDENTITY_SYS_UNITS", t -> ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_SYSTEM"), t.getString("SP_UNITS"))) ); } @@ -586,8 +654,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxString .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.28") .setColumnName("HASH_NORM_PREFIX") - .addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED"))) - .addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT"))) + .addCalculator("HASH_NORM_PREFIX", t -> ResourceIndexedSearchParamString.calculateHashNormalized(new PartitionSettings(), null, new ModelConfig(), t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_VALUE_NORMALIZED"))) + .addCalculator("HASH_EXACT", t -> ResourceIndexedSearchParamString.calculateHashExact(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE_EXACT"))) ); } @@ -634,10 +702,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxToken .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.39") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) - .addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))) - .addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))) - .addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))) + .addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))) + .addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))) ); } @@ -664,8 +732,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxUri .addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.44") .setColumnName("HASH_IDENTITY") - .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))) - .addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI"))) + .addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))) + .addCalculator("HASH_URI", t -> ResourceIndexedSearchParamUri.calculateHashUri(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"), t.getString("SP_URI"))) ); } @@ -698,7 +766,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { Boolean present = columnToBoolean(t.get("SP_PRESENT")); String resType = (String) t.get("RES_TYPE"); String paramName = (String) t.get("PARAM_NAME"); - Long hash = SearchParamPresent.calculateHashPresence(resType, paramName, present); + Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), null, resType, paramName, present); consolidateSearchParamPresenceIndexesTask.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENCE = ? where PID = ?", hash, pid); }); version.addTask(consolidateSearchParamPresenceIndexesTask); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java index ea6431e8fbb..e247953b241 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java @@ -61,6 +61,7 @@ public class Builder { return this; } + @SuppressWarnings("unused") public Builder initializeSchema(String theVersion, String theSchemaName, ISchemaInitializationProvider theSchemaInitializationProvider) { InitializeSchemaTask task = new InitializeSchemaTask(myRelease, theVersion, theSchemaInitializationProvider); task.setDescription("Initialize " + theSchemaName + " schema"); @@ -207,17 +208,18 @@ public class Builder { return new BuilderWithTableName.BuilderAddColumnWithName(myRelease, theVersion, theColumnName, this); } - public void dropColumn(String theVersion, String theColumnName) { + public BuilderCompleteTask dropColumn(String theVersion, String theColumnName) { Validate.notBlank(theColumnName); DropColumnTask task = new DropColumnTask(myRelease, theVersion); task.setTableName(myTableName); task.setColumnName(theColumnName); addTask(task); + return new BuilderCompleteTask(task); } @Override public void addTask(BaseTask theTask) { - ((BaseTableTask) theTask).setTableName(myTableName); + ((BaseTableTask) theTask).setTableName(myTableName); mySink.addTask(theTask); } @@ -457,9 +459,9 @@ public class Builder { public static class BuilderCompleteTask { - private final BaseTask myTask; + private final BaseTask myTask; - public BuilderCompleteTask(BaseTask theTask) { + public BuilderCompleteTask(BaseTask theTask) { myTask = theTask; } diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/MigrationTaskSkipperTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/MigrationTaskSkipperTest.java index b1d3a4db886..f8b2473afda 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/MigrationTaskSkipperTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/MigrationTaskSkipperTest.java @@ -2,10 +2,10 @@ package ca.uhn.fhir.jpa.migrate; import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; import ca.uhn.fhir.jpa.migrate.taskdef.DropIndexTask; -import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -108,8 +108,8 @@ public class MigrationTaskSkipperTest { assertThat(taskVersions, equalTo(expectedVersions)); } - @NotNull - private Stream integersToVersions(Integer[] theVersions) { + @Nonnull +private Stream integersToVersions(Integer[] theVersions) { return Stream.of(theVersions).map(s -> VERSION_PREFIX + s); } } diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java index 8ddf044a879..ee6867ba74b 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java @@ -13,13 +13,20 @@ import javax.annotation.Nonnull; import java.sql.SQLException; import java.util.Properties; import java.util.Set; +import java.util.function.Supplier; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.Matchers.endsWith; import static org.junit.Assert.*; public class SchemaMigratorTest extends BaseTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaMigratorTest.class); + public SchemaMigratorTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testMigrationRequired() { SchemaMigrator schemaMigrator = createTableMigrator(); @@ -28,7 +35,8 @@ public class SchemaMigratorTest extends BaseTest { schemaMigrator.validate(); fail(); } catch (ConfigurationException e) { - assertEquals("The database schema for " + getUrl() + " is out of date. Current database schema version is unknown. Schema version required by application is 1.1. Please run the database migrator.", e.getMessage()); + assertThat(e.getMessage(), startsWith("The database schema for ")); + assertThat(e.getMessage(), endsWith(" is out of date. Current database schema version is unknown. Schema version required by application is 1.1. Please run the database migrator.")); } schemaMigrator.migrate(); @@ -71,28 +79,28 @@ public class SchemaMigratorTest extends BaseTest { public void testSkipSchemaVersion() throws SQLException { AddTableRawSqlTask taskA = new AddTableRawSqlTask("V4_1_0", "20191214.1"); taskA.setTableName("SOMETABLE_A"); - taskA.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE_A (PID bigint not null, TEXTCOL varchar(255))"); + taskA.addSql(getDriverType(), "create table SOMETABLE_A (PID bigint not null, TEXTCOL varchar(255))"); AddTableRawSqlTask taskB = new AddTableRawSqlTask("V4_1_0", "20191214.2"); taskB.setTableName("SOMETABLE_B"); - taskB.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE_B (PID bigint not null, TEXTCOL varchar(255))"); + taskB.addSql(getDriverType(), "create table SOMETABLE_B (PID bigint not null, TEXTCOL varchar(255))"); AddTableRawSqlTask taskC = new AddTableRawSqlTask("V4_1_0", "20191214.3"); taskC.setTableName("SOMETABLE_C"); - taskC.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE_C (PID bigint not null, TEXTCOL varchar(255))"); + taskC.addSql(getDriverType(), "create table SOMETABLE_C (PID bigint not null, TEXTCOL varchar(255))"); AddTableRawSqlTask taskD = new AddTableRawSqlTask("V4_1_0", "20191214.4"); taskD.setTableName("SOMETABLE_D"); - taskD.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE_D (PID bigint not null, TEXTCOL varchar(255))"); + taskD.addSql(getDriverType(), "create table SOMETABLE_D (PID bigint not null, TEXTCOL varchar(255))"); ImmutableList taskList = ImmutableList.of(taskA, taskB, taskC, taskD); MigrationTaskSkipper.setDoNothingOnSkippedTasks(taskList, "4_1_0.20191214.2, 4_1_0.20191214.4"); SchemaMigrator schemaMigrator = new SchemaMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), taskList); - schemaMigrator.setDriverType(DriverTypeEnum.H2_EMBEDDED); + schemaMigrator.setDriverType(getDriverType()); schemaMigrator.migrate(); - DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.H2_EMBEDDED.newConnectionProperties(getDataSource().getUrl(), getDataSource().getUsername(), getDataSource().getPassword()); + DriverTypeEnum.ConnectionProperties connectionProperties = super.getDriverType().newConnectionProperties(getDataSource().getUrl(), getDataSource().getUsername(), getDataSource().getPassword()); Set tableNames = JdbcUtils.getTableNames(connectionProperties); assertThat(tableNames, Matchers.containsInAnyOrder("SOMETABLE_A", "SOMETABLE_C")); } @@ -100,7 +108,7 @@ public class SchemaMigratorTest extends BaseTest { @Test public void testMigrationRequiredNoFlyway() throws SQLException { SchemaMigrator schemaMigrator = createTableMigrator(); - schemaMigrator.setDriverType(DriverTypeEnum.H2_EMBEDDED); + schemaMigrator.setDriverType(getDriverType()); schemaMigrator.setDontUseFlyway(true); // Validate shouldn't fail if we aren't using Flyway @@ -110,7 +118,7 @@ public class SchemaMigratorTest extends BaseTest { schemaMigrator.validate(); - DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.H2_EMBEDDED.newConnectionProperties(getDataSource().getUrl(), getDataSource().getUsername(), getDataSource().getPassword()); + DriverTypeEnum.ConnectionProperties connectionProperties = getDriverType().newConnectionProperties(getDataSource().getUrl(), getDataSource().getUsername(), getDataSource().getPassword()); Set tableNames = JdbcUtils.getTableNames(connectionProperties); assertThat(tableNames, Matchers.contains("SOMETABLE")); @@ -125,9 +133,9 @@ public class SchemaMigratorTest extends BaseTest { private SchemaMigrator createSchemaMigrator(String theTableName, String theSql, String theSchemaVersion) { AddTableRawSqlTask task = new AddTableRawSqlTask("1", theSchemaVersion); task.setTableName(theTableName); - task.addSql(DriverTypeEnum.H2_EMBEDDED, theSql); + task.addSql(getDriverType(), theSql); SchemaMigrator retval = new SchemaMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), ImmutableList.of(task)); - retval.setDriverType(DriverTypeEnum.H2_EMBEDDED); + retval.setDriverType(getDriverType()); return retval; } } diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTest.java index 23bd61a2b8b..e32f531400b 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddColumnTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import java.sql.SQLException; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -18,6 +19,10 @@ import static org.junit.Assert.fail; public class AddColumnTest extends BaseTest { + public AddColumnTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testColumnDoesntAlreadyExist() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java index c95854d1512..c06f96a60d8 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddForeignKeyTaskTest.java @@ -5,12 +5,17 @@ import org.hamcrest.Matchers; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertThat; public class AddForeignKeyTaskTest extends BaseTest { + public AddForeignKeyTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testAddForeignKey() throws SQLException { executeSql("create table HOME (PID bigint not null, TEXTCOL varchar(255), primary key (PID))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTaskTest.java index 089bf089df8..5ac110a7e11 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIdGeneratorTaskTest.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.util.VersionEnum; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -15,6 +16,10 @@ import static org.junit.Assert.assertThat; public class AddIdGeneratorTaskTest extends BaseTest { + public AddIdGeneratorTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testAddIdGenerator() throws SQLException { assertThat(JdbcUtils.getSequenceNames(getConnectionProperties()), empty()); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java index 06bcaa974a5..d61b2eecffd 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; @@ -11,6 +12,10 @@ import static org.junit.Assert.assertThat; public class AddIndexTest extends BaseTest { + public AddIndexTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testUniqueConstraintAlreadyExists() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTaskTest.java index 6ecb34dcc2a..4ae1c9bdae9 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableByColumnTaskTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.migrate.taskdef; +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.JdbcUtils; import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks; import ca.uhn.fhir.jpa.migrate.tasks.api.Builder; @@ -8,12 +9,17 @@ import org.junit.Test; import java.sql.SQLException; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; public class AddTableByColumnTaskTest extends BaseTest { + public AddTableByColumnTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testAddTable() throws SQLException { @@ -27,7 +33,13 @@ public class AddTableByColumnTaskTest extends BaseTest { .filter(s -> !s.startsWith("FK_REF_INDEX_")) .filter(s -> !s.startsWith("PRIMARY_KEY_")) .collect(Collectors.toSet()); - assertThat(indexes, containsInAnyOrder("IDX_BONJOUR")); + + // Derby auto-creates constraints with a system name for unique indexes + if (getDriverType().equals(DriverTypeEnum.DERBY_EMBEDDED)) { + indexes.removeIf(t->t.startsWith("SQL")); + } + + assertThat(indexes.toString(), indexes, containsInAnyOrder("IDX_BONJOUR")); } private static class MyMigrationTasks extends BaseMigrationTasks { diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableTest.java index ccf66a63b8d..427d9275046 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddTableTest.java @@ -5,18 +5,23 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; public class AddTableTest extends BaseTest { + public AddTableTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testTableDoesntAlreadyExist() throws SQLException { AddTableRawSqlTask task = new AddTableRawSqlTask("1", "1"); task.setTableName("SOMETABLE"); - task.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + task.addSql(getDriverType(), "create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); getMigrator().addTask(task); getMigrator().migrate(); @@ -31,7 +36,7 @@ public class AddTableTest extends BaseTest { AddTableRawSqlTask task = new AddTableRawSqlTask("1", "1"); task.setTableName("SOMETABLE"); - task.addSql(DriverTypeEnum.H2_EMBEDDED, "create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); + task.addSql(getDriverType(), "create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); getMigrator().addTask(task); getMigrator().migrate(); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTaskTest.java index 3de4b454790..24b7c007a75 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ArbitrarySqlTaskTest.java @@ -1,18 +1,23 @@ package ca.uhn.fhir.jpa.migrate.taskdef; -import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; import ca.uhn.fhir.util.VersionEnum; import org.junit.Test; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import static org.junit.Assert.assertEquals; public class ArbitrarySqlTaskTest extends BaseTest { + public ArbitrarySqlTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void test350MigrateSearchParams() { executeSql("create table HFJ_SEARCH_PARM (PID bigint not null, RES_TYPE varchar(255), PARAM_NAME varchar(255))"); @@ -36,7 +41,7 @@ public class ArbitrarySqlTaskTest extends BaseTest { Boolean present = (Boolean) t.get("SP_PRESENT"); String resType = (String) t.get("RES_TYPE"); String paramName = (String) t.get("PARAM_NAME"); - Long hash = SearchParamPresent.calculateHashPresence(resType, paramName, present); + Long hash = SearchParamPresent.calculateHashPresence(new PartitionSettings(), null, resType, paramName, present); task.executeSql("HFJ_RES_PARAM_PRESENT", "update HFJ_RES_PARAM_PRESENT set HASH_PRESENT = ? where PID = ?", hash, pid); }); @@ -106,8 +111,8 @@ public class ArbitrarySqlTaskTest extends BaseTest { }; migrator .forVersion(VersionEnum.V3_5_0) - .executeRawSql("1", DriverTypeEnum.H2_EMBEDDED, "delete from TEST_UPDATE_TASK where RES_TYPE = 'Patient'") - .executeRawSql("2", DriverTypeEnum.H2_EMBEDDED, "delete from TEST_UPDATE_TASK where RES_TYPE = 'Encounter'"); + .executeRawSql("1", getDriverType(), "delete from TEST_UPDATE_TASK where RES_TYPE = 'Patient'") + .executeRawSql("2", getDriverType(), "delete from TEST_UPDATE_TASK where RES_TYPE = 'Encounter'"); getMigrator().addTasks(migrator.getTasks(VersionEnum.V3_3_0, VersionEnum.V3_6_0)); getMigrator().migrate(); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java index b039a06097c..f4faff0a52a 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTest.java @@ -8,21 +8,46 @@ import org.apache.commons.dbcp2.BasicDataSource; import org.intellij.lang.annotations.Language; import org.junit.After; import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.ColumnMapRowMapper; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; -public class BaseTest { + +@RunWith(Parameterized.class) +public abstract class BaseTest { private static final String DATABASE_NAME = "DATABASE"; + private static final Logger ourLog = LoggerFactory.getLogger(BaseTest.class); private static int ourDatabaseUrl = 0; + private final Supplier myTestDatabaseDetails; + private BasicDataSource myDataSource; private String myUrl; private FlywayMigrator myMigrator; private DriverTypeEnum.ConnectionProperties myConnectionProperties; - private final BasicDataSource myDataSource = new BasicDataSource(); + + public BaseTest(Supplier theTestDatabaseDetails) { + myTestDatabaseDetails = theTestDatabaseDetails; + } + + @Before + public void before() { + TestDatabaseDetails testDatabaseDetails = myTestDatabaseDetails.get(); + + myUrl = testDatabaseDetails.myUrl; + myConnectionProperties = testDatabaseDetails.myConnectionProperties; + myDataSource = testDatabaseDetails.myDataSource; + myMigrator = testDatabaseDetails.myMigrator; + } public String getUrl() { return myUrl; @@ -32,19 +57,6 @@ public class BaseTest { return myConnectionProperties; } - @Before() - public void before() { - org.h2.Driver.class.toString(); - myUrl = "jdbc:h2:mem:" + DATABASE_NAME + ourDatabaseUrl++; - - myConnectionProperties = DriverTypeEnum.H2_EMBEDDED.newConnectionProperties(myUrl, "SA", "SA"); - myDataSource.setUrl(myUrl); - myDataSource.setUsername("SA"); - myDataSource.setPassword("SA"); - myDataSource.setDriverClassName(DriverTypeEnum.H2_EMBEDDED.getDriverClassName()); - myMigrator = new FlywayMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, myDataSource, DriverTypeEnum.H2_EMBEDDED); - } - protected BasicDataSource getDataSource() { return myDataSource; } @@ -79,5 +91,75 @@ public class BaseTest { myConnectionProperties.close(); } + protected DriverTypeEnum getDriverType() { + return myConnectionProperties.getDriverType(); + } + + public static class TestDatabaseDetails { + + private final String myUrl; + private final DriverTypeEnum.ConnectionProperties myConnectionProperties; + private final BasicDataSource myDataSource; + private final FlywayMigrator myMigrator; + + public TestDatabaseDetails(String theUrl, DriverTypeEnum.ConnectionProperties theConnectionProperties, BasicDataSource theDataSource, FlywayMigrator theMigrator) { + myUrl = theUrl; + myConnectionProperties = theConnectionProperties; + myDataSource = theDataSource; + myMigrator = theMigrator; + } + + } + + @Parameterized.Parameters(name = "{0}") + public static Collection> data() { + ourLog.info("H2: {}", org.h2.Driver.class.toString()); + + ArrayList> retVal = new ArrayList<>(); + + // H2 + retVal.add(new Supplier() { + @Override + public TestDatabaseDetails get() { + String url = "jdbc:h2:mem:" + DATABASE_NAME + ourDatabaseUrl++; + DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.H2_EMBEDDED.newConnectionProperties(url, "SA", "SA"); + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setUrl(url); + dataSource.setUsername("SA"); + dataSource.setPassword("SA"); + dataSource.setDriverClassName(DriverTypeEnum.H2_EMBEDDED.getDriverClassName()); + FlywayMigrator migrator = new FlywayMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, dataSource, DriverTypeEnum.H2_EMBEDDED); + return new TestDatabaseDetails(url, connectionProperties, dataSource, migrator); + } + + @Override + public String toString() { + return "H2"; + } + }); + + // Derby + retVal.add(new Supplier() { + @Override + public TestDatabaseDetails get() { + String url = "jdbc:derby:memory:" + DATABASE_NAME + ourDatabaseUrl++ + ";create=true"; + DriverTypeEnum.ConnectionProperties connectionProperties = DriverTypeEnum.DERBY_EMBEDDED.newConnectionProperties(url, "SA", "SA"); + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setUrl(url); + dataSource.setUsername("SA"); + dataSource.setPassword("SA"); + dataSource.setDriverClassName(DriverTypeEnum.DERBY_EMBEDDED.getDriverClassName()); + FlywayMigrator migrator = new FlywayMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, dataSource, DriverTypeEnum.DERBY_EMBEDDED); + return new TestDatabaseDetails(url, connectionProperties, dataSource, migrator); + } + + @Override + public String toString() { + return "Derby"; + } + }); + + return retVal; + } } diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTest.java index b6637794856..bac8cff0e48 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/CalculateHashesTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.migrate.taskdef; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.util.VersionEnum; @@ -7,11 +8,16 @@ import org.junit.Test; import org.springframework.jdbc.core.JdbcTemplate; import java.util.Map; +import java.util.function.Supplier; import static org.junit.Assert.assertEquals; public class CalculateHashesTest extends BaseTest { + public CalculateHashesTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testCreateHashes() { executeSql("create table HFJ_SPIDX_TOKEN (SP_ID bigint not null, SP_MISSING boolean, SP_NAME varchar(100) not null, RES_ID bigint, RES_TYPE varchar(255) not null, SP_UPDATED timestamp, HASH_IDENTITY bigint, HASH_SYS bigint, HASH_SYS_AND_VALUE bigint, HASH_VALUE bigint, SP_SYSTEM varchar(200), SP_VALUE varchar(200), primary key (SP_ID))"); @@ -21,10 +27,10 @@ public class CalculateHashesTest extends BaseTest { CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1"); task.setTableName("HFJ_SPIDX_TOKEN"); task.setColumnName("HASH_IDENTITY"); - task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))); - task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))); - task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))); - task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))); + task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))); + task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))); + task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))); + task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))); task.setBatchSize(1); getMigrator().addTask(task); @@ -68,10 +74,10 @@ public class CalculateHashesTest extends BaseTest { CalculateHashesTask task = new CalculateHashesTask(VersionEnum.V3_5_0, "1"); task.setTableName("HFJ_SPIDX_TOKEN"); task.setColumnName("HASH_IDENTITY"); - task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME"))); - task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))); - task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))); - task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))); + task.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), null, t.getResourceType(), t.getString("SP_NAME"))); + task.addCalculator("HASH_SYS", t -> ResourceIndexedSearchParamToken.calculateHashSystem(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"))); + task.addCalculator("HASH_SYS_AND_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_SYSTEM"), t.getString("SP_VALUE"))); + task.addCalculator("HASH_VALUE", t -> ResourceIndexedSearchParamToken.calculateHashValue(new PartitionSettings(), null, t.getResourceType(), t.getParamName(), t.getString("SP_VALUE"))); task.setBatchSize(3); getMigrator().addTask(task); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTest.java index 268e1d79fd2..e5b399df3e0 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropColumnTest.java @@ -4,12 +4,17 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; public class DropColumnTest extends BaseTest { + public DropColumnTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testDropColumn() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTaskTest.java index 906877a78a7..f615799d382 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropForeignKeyTaskTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.empty; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; @@ -11,6 +12,10 @@ import static org.junit.Assert.assertThat; public class DropForeignKeyTaskTest extends BaseTest { + public DropForeignKeyTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testDropForeignKey() throws SQLException { executeSql("create table PARENT (PID bigint not null, TEXTCOL varchar(255), primary key (PID))"); @@ -19,7 +24,7 @@ public class DropForeignKeyTaskTest extends BaseTest { assertThat(JdbcUtils.getForeignKeys(getConnectionProperties(), "PARENT", "CHILD"), hasSize(1)); - DropForeignKeyTask task = new DropForeignKeyTask("1", "1"); + DropForeignKeyTask task = new DropForeignKeyTask("1", "1"); task.setTableName("CHILD"); task.setParentTableName("PARENT"); task.setConstraintName("FK_MOM"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTaskTest.java index e4ad65c9cd4..618bbc91194 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIdGeneratorTaskTest.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.util.VersionEnum; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -15,6 +16,10 @@ import static org.junit.Assert.assertThat; public class DropIdGeneratorTaskTest extends BaseTest { + public DropIdGeneratorTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testAddIdGenerator() throws SQLException { executeSql("create sequence SEQ_FOO start with 1 increment by 50"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java index 88d5c42f8a6..778c8b44165 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropIndexTest.java @@ -4,12 +4,17 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; public class DropIndexTest extends BaseTest { + public DropIndexTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testIndexAlreadyExists() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTest.java index 16350002f9f..d81877227b9 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/DropTableTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.*; import static org.hamcrest.core.IsNot.not; @@ -11,6 +12,10 @@ import static org.junit.Assert.assertThat; public class DropTableTest extends BaseTest { + public DropTableTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testDropExistingTable() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); @@ -32,7 +37,7 @@ public class DropTableTest extends BaseTest { public void testDropTableWithForeignKey() throws SQLException { executeSql("create table FOREIGNTABLE (PID bigint not null, TEXTCOL varchar(255), primary key (PID))"); executeSql("create table SOMETABLE (PID bigint not null, REMOTEPID bigint not null, primary key (PID))"); - executeSql("alter table SOMETABLE add constraint FK_MYFK foreign key (REMOTEPID) references FOREIGNTABLE;"); + executeSql("alter table SOMETABLE add constraint FK_MYFK foreign key (REMOTEPID) references FOREIGNTABLE"); DropTableTask task = new DropTableTask("1", "1"); task.setTableName("SOMETABLE"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTaskTest.java index 96ec78d6aa6..b22e36ec461 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ExecuteRawSqlTaskTest.java @@ -6,11 +6,16 @@ import org.junit.Test; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import static org.junit.Assert.assertEquals; public class ExecuteRawSqlTaskTest extends BaseTest { + public ExecuteRawSqlTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testExecuteSql() { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTaskTest.java index c65bcb06006..59a886bf1dc 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/InitializeSchemaTaskTest.java @@ -9,12 +9,17 @@ import org.junit.Test; import java.sql.SQLException; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; public class InitializeSchemaTaskTest extends BaseTest { + public InitializeSchemaTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testInitializeTwice() throws SQLException { InitializeSchemaTask task = new InitializeSchemaTask("1", "1", new TestProvider()); @@ -62,7 +67,7 @@ public class InitializeSchemaTaskTest extends BaseTest { } private int size() { - return getSqlStatements(DriverTypeEnum.H2_EMBEDDED).size(); + return getSqlStatements(getDriverType()).size(); } // This could be stricter, but we don't want this to be brittle. diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java index d34d4bb942e..9fc8f2105d6 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java @@ -1,17 +1,27 @@ package ca.uhn.fhir.jpa.migrate.taskdef; +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.JdbcUtils; import org.flywaydb.core.internal.command.DbMigrate; import org.junit.Test; import java.sql.SQLException; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*; public class ModifyColumnTest extends BaseTest { + public ModifyColumnTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testColumnWithJdbcTypeClob() throws SQLException { + if (getDriverType() == DriverTypeEnum.DERBY_EMBEDDED) { + return; + } + executeSql("create table SOMETABLE (TEXTCOL clob)"); ModifyColumnTask task = new ModifyColumnTask("1", "1"); @@ -126,7 +136,7 @@ public class ModifyColumnTest extends BaseTest { assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); assertFalse(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "DATECOL")); assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG, 19), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); - assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP, 26), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "DATECOL")); + assertEquals(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP, JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "DATECOL").getColumnTypeEnum()); getMigrator().setNoColumnShrink(true); @@ -152,7 +162,7 @@ public class ModifyColumnTest extends BaseTest { assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "PID")); assertTrue(JdbcUtils.isColumnNullable(getConnectionProperties(), "SOMETABLE", "DATECOL")); assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG, 19), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "PID")); - assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP, 26), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "DATECOL")); + assertEquals(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP, JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "DATECOL").getColumnTypeEnum()); // Make sure additional migrations don't crash getMigrator().migrate(); @@ -269,4 +279,29 @@ public class ModifyColumnTest extends BaseTest { assertTrue(existingColumnType.equals(task.getColumnType(), task.getColumnLength())); } + + @Test + public void testShrinkDoesntFailIfShrinkCannotProceed() throws SQLException { + executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(10))"); + executeSql("insert into SOMETABLE (PID, TEXTCOL) values (1, '0123456789')"); + + ModifyColumnTask task = new ModifyColumnTask("1", "123456.7"); + task.setTableName("SOMETABLE"); + task.setColumnName("TEXTCOL"); + task.setColumnType(AddColumnTask.ColumnTypeEnum.STRING); + task.setNullable(true); + task.setColumnLength(5); + + getMigrator().addTask(task); + getMigrator().migrate(); + + assertEquals(1, task.getExecutedStatements().size()); + assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL")); + + // Make sure additional migrations don't crash + getMigrator().migrate(); + getMigrator().migrate(); + + } + } diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTaskTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTaskTest.java index 8c5accd2a67..f1927501f69 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTaskTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/RenameColumnTaskTest.java @@ -6,12 +6,19 @@ import org.junit.Test; import java.sql.SQLException; import java.util.Set; +import java.util.function.Supplier; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class RenameColumnTaskTest extends BaseTest { + public RenameColumnTaskTest(Supplier theTestDatabaseDetails) { + super(theTestDatabaseDetails); + } + @Test public void testColumnAlreadyExists() throws SQLException { executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(255))"); diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 83b438d3efc..8bd89b237f5 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -134,6 +134,10 @@ spring-test test + + org.hibernate.validator + hibernate-validator + diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyBundle.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyBundle.java deleted file mode 100644 index 63805b90fb0..00000000000 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyBundle.java +++ /dev/null @@ -1,110 +0,0 @@ -package ca.uhn.fhir.jpa.model.any; - -/*- - * #%L - * HAPI FHIR Model - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseResource; - -public class AnyBundle { - private final FhirVersionEnum myFhirVersion; - private final IBaseBundle myBundle; - - public static AnyBundle fromFhirContext(FhirContext theFhirContext) { - FhirVersionEnum version = theFhirContext.getVersion().getVersion(); - switch (version) { - case DSTU2: - return new AnyBundle(new ca.uhn.fhir.model.dstu2.resource.Bundle()); - case DSTU3: - return new AnyBundle(new org.hl7.fhir.dstu3.model.Bundle()); - case R4: - return new AnyBundle(new org.hl7.fhir.r4.model.Bundle()); - default: - throw new UnsupportedOperationException(version + " not supported"); - } - } - - public AnyBundle(ca.uhn.fhir.model.dstu2.resource.Bundle theBundleR2) { - myFhirVersion = FhirVersionEnum.DSTU2; - myBundle = theBundleR2; - } - - public AnyBundle(org.hl7.fhir.dstu3.model.Bundle theBundleR3) { - myFhirVersion = FhirVersionEnum.DSTU3; - myBundle = theBundleR3; - } - - public AnyBundle(org.hl7.fhir.r4.model.Bundle theBundleR4) { - myFhirVersion = FhirVersionEnum.R4; - myBundle = theBundleR4; - } - - public static AnyBundle fromResource(IBaseResource theBundle) { - if (theBundle instanceof ca.uhn.fhir.model.dstu2.resource.Bundle) { - return new AnyBundle((ca.uhn.fhir.model.dstu2.resource.Bundle) theBundle); - } else if (theBundle instanceof org.hl7.fhir.dstu3.model.Bundle) { - return new AnyBundle((org.hl7.fhir.dstu3.model.Bundle) theBundle); - } else if (theBundle instanceof org.hl7.fhir.r4.model.Bundle) { - return new AnyBundle((org.hl7.fhir.r4.model.Bundle) theBundle); - } else { - throw new UnsupportedOperationException("Cannot convert " + theBundle.getClass().getName() + " to AnyBundle"); - } - } - - public IBaseBundle get() { - return myBundle; - } - - public ca.uhn.fhir.model.dstu2.resource.Bundle getDstu2() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU2); - return (ca.uhn.fhir.model.dstu2.resource.Bundle) get(); - } - - public org.hl7.fhir.dstu3.model.Bundle getDstu3() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); - return (org.hl7.fhir.dstu3.model.Bundle) get(); - } - - public org.hl7.fhir.r4.model.Bundle getR4() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); - return (org.hl7.fhir.r4.model.Bundle) get(); - } - - public void addResource(IBaseResource theResource) { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent entry = new org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent(); - entry.setResource((org.hl7.fhir.dstu3.model.Resource) theResource); - getDstu3().getEntry().add(entry); - break; - case R4: - org.hl7.fhir.r4.model.Bundle.BundleEntryComponent entryr4 = new org.hl7.fhir.r4.model.Bundle.BundleEntryComponent(); - entryr4.setResource((org.hl7.fhir.r4.model.Resource) theResource); - getR4().getEntry().add(entryr4); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - - } -} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java deleted file mode 100644 index 702f63a05f5..00000000000 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java +++ /dev/null @@ -1,235 +0,0 @@ -package ca.uhn.fhir.jpa.model.any; - -/*- - * #%L - * HAPI FHIR Model - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.List; - -public class AnyComposition { - private final FhirVersionEnum myFhirVersion; - private final IBaseResource myComposition; - - public static AnyComposition fromFhirContext(FhirContext theFhirContext) { - FhirVersionEnum version = theFhirContext.getVersion().getVersion(); - switch (version) { - case DSTU3: - return new AnyComposition(new org.hl7.fhir.dstu3.model.Composition()); - case R4: - return new AnyComposition(new org.hl7.fhir.r4.model.Composition()); - default: - throw new UnsupportedOperationException(version + " not supported"); - } - } - - public AnyComposition(org.hl7.fhir.dstu3.model.Composition theCompositionR3) { - myFhirVersion = FhirVersionEnum.DSTU3; - myComposition = theCompositionR3; - } - - public AnyComposition(org.hl7.fhir.r4.model.Composition theCompositionR4) { - myFhirVersion = FhirVersionEnum.R4; - myComposition = theCompositionR4; - } - - public static AnyComposition fromResource(IBaseResource theComposition) { - if (theComposition instanceof org.hl7.fhir.dstu3.model.Composition) { - return new AnyComposition((org.hl7.fhir.dstu3.model.Composition) theComposition); - } else if (theComposition instanceof org.hl7.fhir.r4.model.Composition) { - return new AnyComposition((org.hl7.fhir.r4.model.Composition) theComposition); - } else { - throw new UnsupportedOperationException("Cannot convert " + theComposition.getClass().getName() + " to AnyList"); - } - } - - public IBaseResource get() { - return myComposition; - } - - public org.hl7.fhir.dstu3.model.Composition getDstu3() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); - return (org.hl7.fhir.dstu3.model.Composition) get(); - } - - public org.hl7.fhir.r4.model.Composition getR4() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); - return (org.hl7.fhir.r4.model.Composition) get(); - } - - public void setIdentifier(String theSystem, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setIdentifier(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - case R4: - getR4().setIdentifier(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getIdentifier() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getIdentifier().getValue(); - case R4: - return getR4().getIdentifier().getValue(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setClass(String theSystem, String theCode) { - switch (myFhirVersion) { - case DSTU3: - setClassDstu3(theSystem, theCode); - break; - case R4: - setClassR4(theSystem, theCode); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private void setClassDstu3(String theSystem, String theCode) { - org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); - codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); - getDstu3().setClass_(codeableConcept); - } - - private void setClassR4(String theSystem, String theCode) { - org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept(); - codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); - getR4().addCategory(codeableConcept); - } - - public void addStringExtension(String theUrl, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); - break; - case R4: - getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - // TODO KHS Consolidate with other classes in this package - public String getStringExtensionValueOrNull(String theUrl) { - switch (myFhirVersion) { - case DSTU3: - return getStringExtensionValueOrNullDstu3(theUrl); - case R4: - return getStringExtensionValueOrNullR4(theUrl); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private String getStringExtensionValueOrNullDstu3(String theUrl) { - List targetTypes = getDstu3().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - private String getStringExtensionValueOrNullR4(String theUrl) { - List targetTypes = getR4().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - public void setSubject(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setSubject(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); - break; - case R4: - getR4().setSubject(new org.hl7.fhir.r4.model.Reference(theReferenceId)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setTitle(String theTitle) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setTitle(theTitle); - break; - case R4: - getR4().setTitle(theTitle); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getTitle() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getTitle(); - case R4: - return getR4().getTitle(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addEntry(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().getSectionFirstRep().addEntry(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); - break; - case R4: - getR4().getSectionFirstRep().addEntry(new org.hl7.fhir.r4.model.Reference(theReferenceId)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setRandomUuid() { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setId(org.hl7.fhir.dstu3.model.IdType.newRandomUuid()); - break; - case R4: - getR4().setId(org.hl7.fhir.r4.model.IdType.newRandomUuid()); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - - } -} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java deleted file mode 100644 index 4ac73881959..00000000000 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java +++ /dev/null @@ -1,361 +0,0 @@ -package ca.uhn.fhir.jpa.model.any; - -/*- - * #%L - * HAPI FHIR Model - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.rest.param.TokenParam; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.List; -import java.util.stream.Stream; - -public class AnyListResource { - private final FhirVersionEnum myFhirVersion; - private final IBaseResource myListResource; - - public static AnyListResource fromFhirContext(FhirContext theFhirContext) { - FhirVersionEnum version = theFhirContext.getVersion().getVersion(); - switch (version) { - case DSTU2: - return new AnyListResource(new ca.uhn.fhir.model.dstu2.resource.ListResource()); - case DSTU3: - return new AnyListResource(new org.hl7.fhir.dstu3.model.ListResource()); - case R4: - return new AnyListResource(new org.hl7.fhir.r4.model.ListResource()); - case R5: - return new AnyListResource(new org.hl7.fhir.r5.model.ListResource()); - default: - throw new UnsupportedOperationException(version + " not supported"); - } - } - - public AnyListResource(ca.uhn.fhir.model.dstu2.resource.ListResource theListResourceR2) { - myFhirVersion = FhirVersionEnum.DSTU2; - myListResource = theListResourceR2; - } - - public AnyListResource(org.hl7.fhir.dstu3.model.ListResource theListResourceR3) { - myFhirVersion = FhirVersionEnum.DSTU3; - myListResource = theListResourceR3; - } - - public AnyListResource(org.hl7.fhir.r4.model.ListResource theListResourceR4) { - myFhirVersion = FhirVersionEnum.R4; - myListResource = theListResourceR4; - } - - public AnyListResource(org.hl7.fhir.r5.model.ListResource theListResourceR5) { - myFhirVersion = FhirVersionEnum.R5; - myListResource = theListResourceR5; - } - - public static AnyListResource fromResource(IBaseResource theListResource) { - if (theListResource instanceof ca.uhn.fhir.model.dstu2.resource.ListResource) { - return new AnyListResource((ca.uhn.fhir.model.dstu2.resource.ListResource) theListResource); - } else if (theListResource instanceof org.hl7.fhir.dstu3.model.ListResource) { - return new AnyListResource((org.hl7.fhir.dstu3.model.ListResource) theListResource); - } else if (theListResource instanceof org.hl7.fhir.r4.model.ListResource) { - return new AnyListResource((org.hl7.fhir.r4.model.ListResource) theListResource); - } else if (theListResource instanceof org.hl7.fhir.r5.model.ListResource) { - return new AnyListResource((org.hl7.fhir.r5.model.ListResource) theListResource); - } else { - throw new UnsupportedOperationException("Cannot convert " + theListResource.getClass().getName() + " to AnyList"); - } - } - - public IBaseResource get() { - return myListResource; - } - - public ca.uhn.fhir.model.dstu2.resource.ListResource getDstu2() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU2); - return (ca.uhn.fhir.model.dstu2.resource.ListResource) get(); - } - - public org.hl7.fhir.dstu3.model.ListResource getDstu3() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); - return (org.hl7.fhir.dstu3.model.ListResource) get(); - } - - public org.hl7.fhir.r4.model.ListResource getR4() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); - return (org.hl7.fhir.r4.model.ListResource) get(); - } - - public org.hl7.fhir.r5.model.ListResource getR5() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.R5); - return (org.hl7.fhir.r5.model.ListResource) get(); - } - - public FhirVersionEnum getFhirVersion() { - return myFhirVersion; - } - - public void addCode(String theSystem, String theCode) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().getCode().addCoding().setSystem(theSystem).setCode(theCode); - break; - case R4: - getR4().getCode().addCoding().setSystem(theSystem).setCode(theCode); - break; - case R5: - getR5().getCode().addCoding().setSystem(theSystem).setCode(theCode); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addIdentifier(String theSystem, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - case R4: - getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - case R5: - getR5().getIdentifier().add(new org.hl7.fhir.r5.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addStringExtension(String theUrl, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); - break; - case R4: - getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); - break; - case R5: - getR5().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r5.model.StringType(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getStringExtensionValueOrNull(String theUrl) { - switch (myFhirVersion) { - case DSTU3: - return getStringExtensionValueOrNullDstu3(theUrl); - case R4: - return getStringExtensionValueOrNullR4(theUrl); - case R5: - return getStringExtensionValueOrNullR5(theUrl); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private String getStringExtensionValueOrNullDstu3(String theUrl) { - List targetTypes = getDstu3().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - private String getStringExtensionValueOrNullR4(String theUrl) { - List targetTypes = getR4().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - private String getStringExtensionValueOrNullR5(String theUrl) { - List targetTypes = getR5().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.r5.model.StringType targetType = (org.hl7.fhir.r5.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - public void addReference(IBaseReference theReference) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().addEntry().setItem((org.hl7.fhir.dstu3.model.Reference) theReference); - break; - case R4: - getR4().addEntry().setItem((org.hl7.fhir.r4.model.Reference) theReference); - break; - case R5: - getR5().addEntry().setItem((org.hl7.fhir.r5.model.Reference) theReference); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addReference(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().addEntry().setItem(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); - break; - case R4: - getR4().addEntry().setItem(new org.hl7.fhir.r4.model.Reference(theReferenceId)); - break; - case R5: - getR5().addEntry().setItem(new org.hl7.fhir.r5.model.Reference(theReferenceId)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public Stream getReferenceStream() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getEntry().stream() - .map(entry -> entry.getItem().getReference()) - .map(reference -> new org.hl7.fhir.dstu3.model.IdType(reference).toUnqualifiedVersionless().getValue()); - case R4: - return getR4().getEntry().stream() - .map(entry -> entry.getItem().getReference()) - .map(reference -> new org.hl7.fhir.r4.model.IdType(reference).toUnqualifiedVersionless().getValue()); - case R5: - return getR5().getEntry().stream() - .map(entry -> entry.getItem().getReference()) - .map(reference -> new org.hl7.fhir.r5.model.IdType(reference).toUnqualifiedVersionless().getValue()); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public boolean removeItem(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - return removeItemDstu3(theReferenceId); - case R4: - return removeItemR4(theReferenceId); - case R5: - return removeItemR5(theReferenceId); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private boolean removeItemDstu3(String theReferenceId) { - boolean removed = false; - for (org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent entry : getDstu3().getEntry()) { - if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) { - entry.setDeleted(true); - removed = true; - break; - } - } - - if (removed) { - getDstu3().getEntry().removeIf(entry -> entry.getDeleted()); - } - return removed; - } - - private boolean removeItemR4(String theReferenceId) { - boolean removed = false; - for (org.hl7.fhir.r4.model.ListResource.ListEntryComponent entry : getR4().getEntry()) { - if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) { - entry.setDeleted(true); - removed = true; - break; - } - } - - if (removed) { - getR4().getEntry().removeIf(entry -> entry.getDeleted()); - } - return removed; - } - - private boolean removeItemR5(String theReferenceId) { - boolean removed = false; - for (org.hl7.fhir.r5.model.ListResource.ListResourceEntryComponent entry : getR5().getEntry()) { - if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) { - entry.setDeleted(true); - removed = true; - break; - } - } - - if (removed) { - getR5().getEntry().removeIf(entry -> entry.getDeleted()); - } - return removed; - } - - public TokenParam getCodeFirstRep() { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getCode().getCodingFirstRep(); - return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); - case R4: - org.hl7.fhir.r4.model.Coding codingR4 = getR4().getCode().getCodingFirstRep(); - return new TokenParam(codingR4.getSystem(), codingR4.getCode()); - case R5: - org.hl7.fhir.r5.model.Coding codingR5 = getR5().getCode().getCodingFirstRep(); - return new TokenParam(codingR5.getSystem(), codingR5.getCode()); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public TokenParam getIdentifierirstRep() { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.Identifier identDstu3 = getDstu3().getIdentifierFirstRep(); - return new TokenParam(identDstu3.getSystem(), identDstu3.getValue()); - case R4: - org.hl7.fhir.r4.model.Identifier identR4 = getR4().getIdentifierFirstRep(); - return new TokenParam(identR4.getSystem(), identR4.getValue()); - case R5: - org.hl7.fhir.r5.model.Identifier identR5 = getR5().getIdentifierFirstRep(); - return new TokenParam(identR5.getSystem(), identR5.getValue()); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - - - public boolean isEmpty() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getEntry().isEmpty(); - case R4: - return getR4().getEntry().isEmpty(); - case R5: - return getR5().getEntry().isEmpty(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } -} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java deleted file mode 100644 index c0ec30848f7..00000000000 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java +++ /dev/null @@ -1,451 +0,0 @@ -package ca.uhn.fhir.jpa.model.any; - -/*- - * #%L - * HAPI FHIR Model - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.rest.param.TokenParam; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class AnyMeasure { - private final FhirVersionEnum myFhirVersion; - private final IBaseResource myMeasure; - - public static AnyMeasure fromFhirContext(FhirContext theFhirContext) { - FhirVersionEnum version = theFhirContext.getVersion().getVersion(); - switch (version) { - case DSTU3: - return new AnyMeasure(new org.hl7.fhir.dstu3.model.Measure()); - case R4: - return new AnyMeasure(new org.hl7.fhir.r4.model.Measure()); - default: - throw new UnsupportedOperationException(version + " not supported"); - } - } - - public AnyMeasure(org.hl7.fhir.dstu3.model.Measure theMeasureR3) { - myFhirVersion = FhirVersionEnum.DSTU3; - myMeasure = theMeasureR3; - } - - public AnyMeasure(org.hl7.fhir.r4.model.Measure theMeasureR4) { - myFhirVersion = FhirVersionEnum.R4; - myMeasure = theMeasureR4; - } - - public static AnyMeasure fromResource(IBaseResource theMeasure) { - if (theMeasure instanceof org.hl7.fhir.dstu3.model.Measure) { - return new AnyMeasure((org.hl7.fhir.dstu3.model.Measure) theMeasure); - } else if (theMeasure instanceof org.hl7.fhir.r4.model.Measure) { - return new AnyMeasure((org.hl7.fhir.r4.model.Measure) theMeasure); - } else { - throw new UnsupportedOperationException("Cannot convert " + theMeasure.getClass().getName() + " to AnyList"); - } - } - - public IBaseResource get() { - return myMeasure; - } - - public org.hl7.fhir.dstu3.model.Measure getDstu3() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); - return (org.hl7.fhir.dstu3.model.Measure) get(); - } - - public org.hl7.fhir.r4.model.Measure getR4() { - Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); - return (org.hl7.fhir.r4.model.Measure) get(); - } - - public void addIdentifier(String theSystem, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - case R4: - getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addType(String theSystem, String theCode) { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); - codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); - getDstu3().getType().add(codeableConcept); - break; - case R4: - org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = new org.hl7.fhir.r4.model.CodeableConcept(); - codeableConceptR4.addCoding().setSystem(theSystem).setCode(theCode); - getR4().getType().add(codeableConceptR4); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void addStringExtension(String theUrl, String theValue) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); - break; - case R4: - getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getStringExtensionValueOrNull(String theUrl) { - switch (myFhirVersion) { - case DSTU3: - return getStringExtensionValueOrNullDstu3(theUrl); - case R4: - return getStringExtensionValueOrNullR4(theUrl); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private String getStringExtensionValueOrNullDstu3(String theUrl) { - List targetTypes = getDstu3().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - private String getStringExtensionValueOrNullR4(String theUrl) { - List targetTypes = getR4().getExtensionsByUrl(theUrl); - if (targetTypes.size() < 1) { - return null; - } - org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); - return targetType.getValue(); - } - - public String getIdentifierFirstRep() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getIdentifierFirstRep().getValue(); - case R4: - return getR4().getIdentifierFirstRep().getValue(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setComposedOf(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); - break; - case R4: - getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private void getRelatedArtifactDstu3(String theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theArtifactType) { - org.hl7.fhir.dstu3.model.RelatedArtifact artifact = new org.hl7.fhir.dstu3.model.RelatedArtifact(); - artifact.setType(theArtifactType); - artifact.setResource(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); - getDstu3().getRelatedArtifact().add(artifact); - } - - private void getRelatedArtifactR4(String theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theArtifactType) { - org.hl7.fhir.r4.model.RelatedArtifact artifact = new org.hl7.fhir.r4.model.RelatedArtifact(); - artifact.setType(theArtifactType); - artifact.setResource(theReferenceId); - getR4().getRelatedArtifact().add(artifact); - } - - public IBaseReference getComposedOf() { - switch (myFhirVersion) { - case DSTU3: - return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); - case R4: - return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setPredecessor(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); - break; - case R4: - getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - - public IBaseReference getPredecessor() { - switch (myFhirVersion) { - case DSTU3: - return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); - case R4: - return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public IBaseReference getDerivedFrom() { - switch (myFhirVersion) { - case DSTU3: - return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); - case R4: - return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setDerivedFrom(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); - break; - case R4: - getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public IBaseReference getSuccessor() { - switch (myFhirVersion) { - case DSTU3: - return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); - case R4: - return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setSuccessor(String theReferenceId) { - switch (myFhirVersion) { - case DSTU3: - getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); - break; - case R4: - getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private IBaseReference getArtifactOfTypeDstu3(org.hl7.fhir.dstu3.model.Measure theMeasure, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theType) { - return theMeasure.getRelatedArtifact() - .stream() - .filter(artifact -> theType == artifact.getType()) - .map(org.hl7.fhir.dstu3.model.RelatedArtifact::getResource) - .findFirst() - .get(); - } - - private IBaseReference getArtifactOfTypeR4(org.hl7.fhir.r4.model.Measure theMeasure, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theType) { - return new org.hl7.fhir.r4.model.Reference(theMeasure.getRelatedArtifact() - .stream() - .filter(artifact -> theType == artifact.getType()) - .map(org.hl7.fhir.r4.model.RelatedArtifact::getResource) - .findFirst() - .get()); - } - - public void setPublisher(String thePublisher) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setPublisher(thePublisher); - break; - case R4: - getR4().setPublisher(thePublisher); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getPublisher() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getPublisher(); - case R4: - return getR4().getPublisher(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setName(String theName) { - switch (myFhirVersion) { - case DSTU3: - getDstu3().setName(theName); - break; - case R4: - getR4().setName(theName); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public String getName() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getName(); - case R4: - return getR4().getName(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setEffectivePeriod(Date start, Date end) { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.Period effectivePeriod = new org.hl7.fhir.dstu3.model.Period(); - effectivePeriod.setStart(start); - effectivePeriod.setEnd(end); - getDstu3().setEffectivePeriod(effectivePeriod); - break; - case R4: - org.hl7.fhir.r4.model.Period effectivePeriodr4 = new org.hl7.fhir.r4.model.Period(); - effectivePeriodr4.setStart(start); - effectivePeriodr4.setEnd(end); - getR4().setEffectivePeriod(effectivePeriodr4); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public Date getEffectivePeriodStart() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getEffectivePeriod().getStart(); - case R4: - return getR4().getEffectivePeriod().getStart(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public Date getEffectivePeriodEnd() { - switch (myFhirVersion) { - case DSTU3: - return getDstu3().getEffectivePeriod().getEnd(); - case R4: - return getR4().getEffectivePeriod().getEnd(); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public void setTopics(List theTokenParamList) { - switch (myFhirVersion) { - case DSTU3: - setTopicsDstu3(theTokenParamList); - break; - case R4: - setTopicsR4(theTokenParamList); - break; - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - private void setTopicsDstu3(List theTokenParamList) { - List topicList = new ArrayList<>(); - - for (TokenParam tokenParam : theTokenParamList) { - org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); - codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue()); - topicList.add(codeableConcept); - } - getDstu3().setTopic(topicList); - } - - private void setTopicsR4(List theTokenParamList) { - List topicList = new ArrayList<>(); - - for (TokenParam tokenParam : theTokenParamList) { - org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept(); - codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue()); - topicList.add(codeableConcept); - } - getR4().setTopic(topicList); - } - - public TokenParam getTopicFirstRep() { - switch (myFhirVersion) { - case DSTU3: - org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopicFirstRep().getCodingFirstRep(); - return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); - case R4: - org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopicFirstRep().getCodingFirstRep(); - return new TokenParam(codingR4.getSystem(), codingR4.getCode()); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } - - public TokenParam getTopicSecondRepOrNull() { - switch (myFhirVersion) { - case DSTU3: - if (getDstu3().getTopic().size() < 2) { - return null; - } - org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopic().get(1).getCodingFirstRep(); - return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); - case R4: - if (getR4().getTopic().size() < 2) { - return null; - } - org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopic().get(1).getCodingFirstRep(); - return new TokenParam(codingR4.getSystem(), codingR4.getCode()); - default: - throw new UnsupportedOperationException(myFhirVersion + " not supported"); - } - } -} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/config/PartitionSettings.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/config/PartitionSettings.java new file mode 100644 index 00000000000..602b78bf1df --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/config/PartitionSettings.java @@ -0,0 +1,110 @@ +package ca.uhn.fhir.jpa.model.config; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2020 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% + */ + +/** + * @since 5.0.0 + */ +public class PartitionSettings { + + private boolean myPartitioningEnabled = false; + private CrossPartitionReferenceMode myAllowReferencesAcrossPartitions = CrossPartitionReferenceMode.NOT_ALLOWED; + private boolean myIncludePartitionInSearchHashes = true; + + /** + * If set to true (default is true) the PARTITION_ID value will be factored into the + * hash values used in the HFJ_SPIDX_xxx tables, removing the need to explicitly add a selector + * on this column in queries. If set to false, an additional selector is used instead, which may perform + * better when using native database partitioning features. + *

        + * This setting has no effect if partitioning is not enabled via {@link #isPartitioningEnabled()}. + *

        + */ + public boolean isIncludePartitionInSearchHashes() { + return myIncludePartitionInSearchHashes; + } + + /** + * If set to true (default is true) the PARTITION_ID value will be factored into the + * hash values used in the HFJ_SPIDX_xxx tables, removing the need to explicitly add a selector + * on this column in queries. If set to false, an additional selector is used instead, which may perform + * better when using native database partitioning features. + *

        + * This setting has no effect if partitioning is not enabled via {@link #isPartitioningEnabled()}. + *

        + */ + public PartitionSettings setIncludePartitionInSearchHashes(boolean theIncludePartitionInSearchHashes) { + myIncludePartitionInSearchHashes = theIncludePartitionInSearchHashes; + return this; + } + + /** + * If enabled (default is false) the JPA server will support data partitioning + * + * @since 5.0.0 + */ + public boolean isPartitioningEnabled() { + return myPartitioningEnabled; + } + + /** + * If enabled (default is false) the JPA server will support data partitioning + * + * @since 5.0.0 + */ + public void setPartitioningEnabled(boolean theMultiTenancyEnabled) { + myPartitioningEnabled = theMultiTenancyEnabled; + } + + /** + * Should resources references be permitted to cross partition boundaries. Default is {@link CrossPartitionReferenceMode#NOT_ALLOWED}. + * + * @since 5.0.0 + */ + public CrossPartitionReferenceMode getAllowReferencesAcrossPartitions() { + return myAllowReferencesAcrossPartitions; + } + + /** + * Should resources references be permitted to cross partition boundaries. Default is {@link CrossPartitionReferenceMode#NOT_ALLOWED}. + * + * @since 5.0.0 + */ + public void setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode theAllowReferencesAcrossPartitions) { + myAllowReferencesAcrossPartitions = theAllowReferencesAcrossPartitions; + } + + + public enum CrossPartitionReferenceMode { + + /** + * References between resources are not allowed to cross partition boundaries + */ + NOT_ALLOWED, + + /** + * References can cross partition boundaries, in a way that hides the existence of partitions to the end user + */ + ALLOWED_UNQUALIFIED + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/IJpaValidationSupportDstu3.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IResourceLookup.java similarity index 77% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/IJpaValidationSupportDstu3.java rename to hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IResourceLookup.java index faa2bfdf3d1..06b9decfc8c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/IJpaValidationSupportDstu3.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IResourceLookup.java @@ -1,10 +1,8 @@ -package ca.uhn.fhir.jpa.dao.dstu3; +package ca.uhn.fhir.jpa.model.cross; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; - -/* +/*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Model * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -22,6 +20,12 @@ import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; * #L% */ -public interface IJpaValidationSupportDstu3 extends IValidationSupport { +import java.util.Date; +public interface IResourceLookup { + String getResourceType(); + + Long getResourceId(); + + Date getDeleted(); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourceLookup.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourceLookup.java new file mode 100644 index 00000000000..661b635e691 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourceLookup.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.model.cross; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2020 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.Date; + +public class ResourceLookup implements IResourceLookup { + private final String myResourceType; + private final Long myResourcePid; + private final Date myDeletedAt; + + public ResourceLookup(String theResourceType, Long theResourcePid, Date theDeletedAt) { + myResourceType = theResourceType; + myResourcePid = theResourcePid; + myDeletedAt = theDeletedAt; + } + + @Override + public String getResourceType() { + return myResourceType; + } + + @Override + public Long getResourceId() { + return myResourcePid; + } + + @Override + public Date getDeleted() { + return myDeletedAt; + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java index 20bb6c1b5f6..5d98279bcff 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.util.ObjectUtil; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; /** * This class is an abstraction for however primary keys are stored in the underlying storage engine. This might be @@ -35,6 +36,7 @@ public class ResourcePersistentId { private Object myId; public ResourcePersistentId(Object theId) { + assert !(theId instanceof Optional); myId = theId; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java index 0118382b324..a1bf1ff9ff7 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java @@ -22,24 +22,26 @@ package ca.uhn.fhir.jpa.model.entity; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.api.Constants; import org.hibernate.annotations.OptimisticLock; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.MappedSuperclass; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; import java.util.Collection; import java.util.Date; @MappedSuperclass -public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersistedResource { +public abstract class BaseHasResource extends BasePartitionable implements IBaseResourceEntity, IBasePersistedResource { @Column(name = "RES_DELETED_AT", nullable = true) @Temporal(TemporalType.TIMESTAMP) private Date myDeleted; - // TODO: move to resource history table @Column(name = "RES_VERSION", nullable = true, length = 7) @Enumerated(EnumType.STRING) @OptimisticLock(excluded = true) @@ -60,7 +62,7 @@ public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersi private Date myUpdated; /** - * This is stored as an optimization to avoid neeind to query for this + * This is stored as an optimization to avoid needing to query for this * after an update */ @Transient diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java new file mode 100644 index 00000000000..83af36ef4d5 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BasePartitionable.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.model.entity; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2020 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.interceptor.model.RequestPartitionId; + +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; + +@MappedSuperclass +public class BasePartitionable implements Serializable { + + @Embedded + private PartitionablePartitionId myPartitionId; + + /** + * This is here to support queries only, do not set this field directly + */ + @SuppressWarnings("unused") + @Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true) + private Integer myPartitionIdValue; + + public RequestPartitionId getPartitionId() { + if (myPartitionId != null) { + return myPartitionId.toPartitionId(); + } else { + return null; + } + } + + public void setPartitionId(@Nullable RequestPartitionId theRequestPartitionId) { + if (theRequestPartitionId != null) { + myPartitionId = new PartitionablePartitionId(theRequestPartitionId.getPartitionId(), theRequestPartitionId.getPartitionDate()); + } else { + myPartitionId = null; + } + } + + +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java index df24e65b0b4..37bb287bdeb 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndex.java @@ -20,9 +20,11 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import javax.persistence.MappedSuperclass; import java.io.Serializable; -public abstract class BaseResourceIndex implements Serializable { +@MappedSuperclass +public abstract class BaseResourceIndex extends BasePartitionable implements Serializable { public abstract Long getId(); @@ -42,4 +44,6 @@ public abstract class BaseResourceIndex implements Serializable { @Override public abstract boolean equals(Object obj); + public abstract void copyMutableValuesFrom(T theSource); + } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java index 4393d0a5a29..b8bc0aca701 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseResourceIndexedSearchParam.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.util.UrlUtil; @@ -31,7 +33,14 @@ import com.google.common.hash.Hashing; import org.hibernate.search.annotations.ContainedIn; import org.hibernate.search.annotations.Field; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; import java.util.Date; @MappedSuperclass @@ -41,16 +50,16 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { * Don't change this without careful consideration. You will break existing hashes! */ private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0); + /** * Don't make this public 'cause nobody better be able to modify it! */ private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8); private static final long serialVersionUID = 1L; - // TODO: make this nullable=false and a primitive (written may 2017) @Field() - @Column(name = "SP_MISSING", nullable = true) - private Boolean myMissing = Boolean.FALSE; + @Column(name = "SP_MISSING", nullable = false) + private boolean myMissing = false; @Field @Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false) @@ -65,13 +74,17 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { private Long myResourcePid; @Field() - @Column(name = "RES_TYPE", nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH) + @Column(name = "RES_TYPE", updatable = false, nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH) private String myResourceType; + @Field() @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3 @Temporal(TemporalType.TIMESTAMP) private Date myUpdated; + @Transient + private transient PartitionSettings myPartitionSettings; + /** * Subclasses may override */ @@ -102,6 +115,14 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { return this; } + @Override + public void copyMutableValuesFrom(T theSource) { + BaseResourceIndexedSearchParam source = (BaseResourceIndexedSearchParam) theSource; + myMissing = source.myMissing; + myParamName = source.myParamName; + myUpdated = source.myUpdated; + } + public Long getResourcePid() { return myResourcePid; } @@ -123,7 +144,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { } public boolean isMissing() { - return Boolean.TRUE.equals(myMissing); + return myMissing; } public BaseResourceIndexedSearchParam setMissing(boolean theMissing) { @@ -137,16 +158,31 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex { throw new UnsupportedOperationException("No parameter matcher for " + theParam); } - public static long calculateHashIdentity(String theResourceType, String theParamName) { - return hash(theResourceType, theParamName); + public PartitionSettings getPartitionSettings() { + return myPartitionSettings; + } + + public BaseResourceIndexedSearchParam setPartitionSettings(PartitionSettings thePartitionSettings) { + myPartitionSettings = thePartitionSettings; + return this; + } + + public static long calculateHashIdentity(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName); } /** * Applies a fast and consistent hashing algorithm to a set of strings */ - static long hash(String... theValues) { + static long hash(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String... theValues) { Hasher hasher = HASH_FUNCTION.newHasher(); + if (thePartitionSettings.isPartitioningEnabled() && thePartitionSettings.isIncludePartitionInSearchHashes() && theRequestPartitionId != null) { + if (theRequestPartitionId.getPartitionId() != null) { + hasher.putInt(theRequestPartitionId.getPartitionId()); + } + } + for (String next : theValues) { if (next == null) { hasher.putByte((byte) 0); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java index 8ba03fca400..43f9bfbaee4 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseTag.java @@ -21,13 +21,14 @@ package ca.uhn.fhir.jpa.model.entity; */ import javax.persistence.Column; +import javax.persistence.Embedded; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; import java.io.Serializable; @MappedSuperclass -public class BaseTag implements Serializable { +public class BaseTag extends BasePartitionable implements Serializable { private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java index 720163bec12..5ace5c24aaf 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ForcedId.java @@ -25,7 +25,7 @@ import org.hibernate.annotations.ColumnDefault; import javax.persistence.*; @Entity() -@Table(name = "HFJ_FORCED_ID", uniqueConstraints = { +@Table(name = ForcedId.HFJ_FORCED_ID, uniqueConstraints = { @UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"}), @UniqueConstraint(name = ForcedId.IDX_FORCEDID_TYPE_FID, columnNames = {"RESOURCE_TYPE", "FORCED_ID"}) }, indexes = { @@ -36,10 +36,11 @@ import javax.persistence.*; * so don't reuse these names */ }) -public class ForcedId { +public class ForcedId extends BasePartitionable { public static final int MAX_FORCED_ID_LENGTH = 100; public static final String IDX_FORCEDID_TYPE_FID = "IDX_FORCEDID_TYPE_FID"; + public static final String HFJ_FORCED_ID = "HFJ_FORCED_ID"; @Column(name = "FORCED_ID", nullable = false, length = MAX_FORCED_ID_LENGTH, updatable = false) private String myForcedId; @@ -93,4 +94,5 @@ public class ForcedId { public Long getId() { return myId; } + } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java index 1938f069ef6..f7616f58f1b 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java @@ -30,7 +30,9 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +// TODO: move this to ca.uhn.fhir.jpa.model.config public class ModelConfig { + /** * Default {@link #getTreatReferencesAsLogical() logical URL bases}. Includes the following * values: @@ -55,10 +57,9 @@ public class ModelConfig { private boolean myAllowExternalReferences = false; private Set myTreatBaseUrlsAsLocal = new HashSet<>(); private Set myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS); - private boolean myDefaultSearchParamsCanBeOverridden = false; + private boolean myDefaultSearchParamsCanBeOverridden = true; private Set mySupportedSubscriptionTypes = new HashSet<>(); private String myEmailFromAddress = "noreply@unknown.com"; - private boolean mySubscriptionMatchingEnabled = true; private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH; /** * Update setter javadoc if default changes. @@ -81,7 +82,7 @@ public class ModelConfig { * the behaviour of the default search parameters. *

        *

        - * The default value for this setting is {@code false} + * The default value for this setting is {@code true} *

        */ public boolean isDefaultSearchParamsCanBeOverridden() { @@ -97,7 +98,7 @@ public class ModelConfig { * the behaviour of the default search parameters. *

        *

        - * The default value for this setting is {@code false} + * The default value for this setting is {@code true} *

        */ public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) { @@ -333,26 +334,6 @@ public class ModelConfig { return Collections.unmodifiableSet(mySupportedSubscriptionTypes); } - /** - * If set to true (default is true) the server will match incoming resources against active subscriptions - * and send them to the subscription channel. If set to false no matching or sending occurs. - * @since 3.7.0 - */ - - public boolean isSubscriptionMatchingEnabled() { - return mySubscriptionMatchingEnabled; - } - - - /** - * If set to true (default is true) the server will match incoming resources against active subscriptions - * and send them to the subscription channel. If set to false no matching or sending occurs. - * @since 3.7.0 - */ - public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) { - mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled; - } - @VisibleForTesting public void clearSupportedSubscriptionTypesForUnitTest() { mySupportedSubscriptionTypes.clear(); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java new file mode 100644 index 00000000000..8ea9849719b --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/PartitionablePartitionId.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.jpa.model.entity; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2020 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.interceptor.model.RequestPartitionId; + +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.time.LocalDate; + +@Embeddable +public class PartitionablePartitionId implements Cloneable { + + static final String PARTITION_ID = "PARTITION_ID"; + + @Column(name = PARTITION_ID, nullable = true, insertable = true, updatable = false) + private Integer myPartitionId; + @Column(name = "PARTITION_DATE", nullable = true, insertable = true, updatable = false) + private LocalDate myPartitionDate; + + /** + * Constructor + */ + public PartitionablePartitionId() { + super(); + } + + /** + * Constructor + */ + public PartitionablePartitionId(@Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { + setPartitionId(thePartitionId); + setPartitionDate(thePartitionDate); + } + + @Nullable + public Integer getPartitionId() { + return myPartitionId; + } + + public PartitionablePartitionId setPartitionId(@Nullable Integer thePartitionId) { + myPartitionId = thePartitionId; + return this; + } + + @Nullable + public LocalDate getPartitionDate() { + return myPartitionDate; + } + + public PartitionablePartitionId setPartitionDate(@Nullable LocalDate thePartitionDate) { + myPartitionDate = thePartitionDate; + return this; + } + + @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "MethodDoesntCallSuperMethod"}) + @Override + protected PartitionablePartitionId clone() { + return new PartitionablePartitionId() + .setPartitionId(getPartitionId()) + .setPartitionDate(getPartitionDate()); + } + + public RequestPartitionId toPartitionId() { + return RequestPartitionId.fromPartitionId(getPartitionId(), getPartitionDate()); + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java index 15dde30bba0..c0fb5226ca5 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java @@ -29,7 +29,7 @@ import javax.persistence.*; @Index(name = "IDX_RESVERPROV_REQUESTID", columnList = "REQUEST_ID") }) @Entity -public class ResourceHistoryProvenanceEntity { +public class ResourceHistoryProvenanceEntity extends BasePartitionable { public static final int SOURCE_URI_LENGTH = 100; @@ -48,18 +48,17 @@ public class ResourceHistoryProvenanceEntity { @Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true) private String myRequestId; - public ResourceTable getResourceTable() { - return myResourceTable; + /** + * Constructor + */ + public ResourceHistoryProvenanceEntity() { + super(); } public void setResourceTable(ResourceTable theResourceTable) { myResourceTable = theResourceTable; } - public ResourceHistoryTable getResourceHistoryTable() { - return myResourceHistoryTable; - } - public void setResourceHistoryTable(ResourceHistoryTable theResourceHistoryTable) { myResourceHistoryTable = theResourceHistoryTable; } @@ -83,4 +82,5 @@ public class ResourceHistoryProvenanceEntity { public Long getId() { return myId; } + } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java index fea5496300e..08f7b2b5d53 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java @@ -94,7 +94,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl } public void addTag(ResourceTag theTag) { - ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag()); + ResourceHistoryTag tag = new ResourceHistoryTag(this, theTag.getTag(), getPartitionId()); tag.setResourceType(theTag.getResourceType()); getTags().add(tag); } @@ -106,7 +106,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl return next; } } - ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag); + ResourceHistoryTag historyTag = new ResourceHistoryTag(this, theTag, getPartitionId()); getTags().add(historyTag); return historyTag; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java index e3482c765da..839d593ff75 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTag.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; + import javax.persistence.*; import java.io.Serializable; @@ -75,11 +77,12 @@ public class ResourceHistoryTag extends BaseTag implements Serializable { } - public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag) { + public ResourceHistoryTag(ResourceHistoryTable theResourceHistoryTable, TagDefinition theTag, RequestPartitionId theRequestPartitionId) { setTag(theTag); setResource(theResourceHistoryTable); setResourceId(theResourceHistoryTable.getResourceId()); setResourceType(theResourceHistoryTable.getResourceType()); + setPartitionId(theRequestPartitionId); } public ResourceHistoryTable getResourceHistory() { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java index 2c6305ebdd8..8b675a97fda 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedCompositeStringUnique.java @@ -21,7 +21,11 @@ package ca.uhn.fhir.jpa.model.entity; */ import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.*; +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import javax.persistence.*; @@ -30,7 +34,7 @@ import javax.persistence.*; @Index(name = ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING, columnList = "IDX_STRING", unique = true), @Index(name = ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_RESOURCE, columnList = "RES_ID", unique = false) }) -public class ResourceIndexedCompositeStringUnique implements Comparable { +public class ResourceIndexedCompositeStringUnique extends BasePartitionable implements Comparable { public static final int MAX_STRING_LENGTH = 200; public static final String IDX_IDXCMPSTRUNIQ_STRING = "IDX_IDXCMPSTRUNIQ_STRING"; @@ -49,6 +53,13 @@ public class ResourceIndexedCompositeStringUnique implements Comparable void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamCoords source = (ResourceIndexedSearchParamCoords) theSource; + myLatitude = source.getLatitude(); + myLongitude = source.getLongitude(); + myHashIdentity = source.myHashIdentity; + } + public void setHashIdentity(Long theHashIdentity) { myHashIdentity = theHashIdentity; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java index 8ef1d524fe2..2119e64ad18 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDate.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.InstantDt; @@ -89,7 +90,8 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar /** * Constructor */ - public ResourceIndexedSearchParamDate(String theResourceType, String theParamName, Date theLow, String theLowString, Date theHigh, String theHighString, String theOriginalValue) { + public ResourceIndexedSearchParamDate(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, Date theLow, String theLowString, Date theHigh, String theHighString, String theOriginalValue) { + setPartitionSettings(thePartitionSettings); setResourceType(theResourceType); setParamName(theParamName); setValueLow(theLow); @@ -132,13 +134,24 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar return myValueHighDateOrdinal; } + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamDate source = (ResourceIndexedSearchParamDate) theSource; + myValueHigh = source.myValueHigh; + myValueLow = source.myValueLow; + myValueHighDateOrdinal = source.myValueHighDateOrdinal; + myValueLowDateOrdinal = source.myValueLowDateOrdinal; + myHashIdentity = source.myHashIdentity; + } + @Override @PrePersist public void calculateHashes() { - if (myHashIdentity == null) { + if (myHashIdentity == null && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); } } @@ -230,12 +243,14 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); - b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("resourceId", getResourcePid()); b.append("valueLow", new InstantDt(getValueLow())); b.append("valueHigh", new InstantDt(getValueHigh())); + b.append("missing", isMissing()); return b.build(); } + @SuppressWarnings("ConstantConditions") @Override public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) { if (!(theParam instanceof DateParam)) { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java index bb686e6cc5f..8e71141210c 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamNumber.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.util.BigDecimalNumericFieldBridge; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.NumberParam; @@ -65,19 +66,29 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP public ResourceIndexedSearchParamNumber() { } - public ResourceIndexedSearchParamNumber(String theResourceType, String theParamName, BigDecimal theValue) { + public ResourceIndexedSearchParamNumber(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue) { + setPartitionSettings(thePartitionSettings); setResourceType(theResourceType); setParamName(theParamName); setValue(theValue); } + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamNumber source = (ResourceIndexedSearchParamNumber) theSource; + myValue = source.myValue; + myHashIdentity = source.myHashIdentity; + } + + @Override @PrePersist public void calculateHashes() { - if (myHashIdentity == null) { + if (myHashIdentity == null && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); } } @@ -106,11 +117,6 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP return b.isEquals(); } - public Long getHashIdentity() { - calculateHashes(); - return myHashIdentity; - } - public void setHashIdentity(Long theHashIdentity) { myHashIdentity = theHashIdentity; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java index 8b9787a63db..c1b5d2617f6 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.util.BigDecimalNumericFieldBridge; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.QuantityParam; @@ -91,8 +93,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc } - public ResourceIndexedSearchParamQuantity(String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) { + public ResourceIndexedSearchParamQuantity(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) { this(); + setPartitionSettings(thePartitionSettings); setResourceType(theResourceType); setParamName(theParamName); setSystem(theSystem); @@ -100,17 +103,30 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc setUnits(theUnits); } + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamQuantity source = (ResourceIndexedSearchParamQuantity) theSource; + mySystem = source.mySystem; + myUnits = source.myUnits; + myValue = source.myValue; + myHashIdentity = source.myHashIdentity; + myHashIdentityAndUnits = source.myHashIdentitySystemAndUnits; + myHashIdentitySystemAndUnits = source.myHashIdentitySystemAndUnits; + } + + @Override @PrePersist public void calculateHashes() { - if (myHashIdentity == null) { + if (myHashIdentity == null && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); String units = getUnits(); String system = getSystem(); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); - setHashIdentityAndUnits(calculateHashUnits(resourceType, paramName, units)); - setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(resourceType, paramName, system, units)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); + setHashIdentityAndUnits(calculateHashUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, units)); + setHashIdentitySystemAndUnits(calculateHashSystemAndUnits(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, units)); } } @@ -176,7 +192,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc @Override public void setId(Long theId) { - myId =theId; + myId = theId; } public String getSystem() { @@ -247,25 +263,25 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc // Only match on system if it wasn't specified String quantityUnitsString = defaultString(quantity.getUnits()); if (quantity.getSystem() == null && isBlank(quantityUnitsString)) { - if (Objects.equals(getValue(),quantity.getValue())) { + if (Objects.equals(getValue(), quantity.getValue())) { retval = true; } } else { String unitsString = defaultString(getUnits()); if (quantity.getSystem() == null) { if (unitsString.equalsIgnoreCase(quantityUnitsString) && - Objects.equals(getValue(),quantity.getValue())) { + Objects.equals(getValue(), quantity.getValue())) { retval = true; } } else if (isBlank(quantityUnitsString)) { if (getSystem().equalsIgnoreCase(quantity.getSystem()) && - Objects.equals(getValue(),quantity.getValue())) { + Objects.equals(getValue(), quantity.getValue())) { retval = true; } } else { if (getSystem().equalsIgnoreCase(quantity.getSystem()) && unitsString.equalsIgnoreCase(quantityUnitsString) && - Objects.equals(getValue(),quantity.getValue())) { + Objects.equals(getValue(), quantity.getValue())) { retval = true; } } @@ -273,12 +289,12 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc return retval; } - public static long calculateHashSystemAndUnits(String theResourceType, String theParamName, String theSystem, String theUnits) { - return hash(theResourceType, theParamName, theSystem, theUnits); + public static long calculateHashSystemAndUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theUnits) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theSystem, theUnits); } - public static long calculateHashUnits(String theResourceType, String theParamName, String theUnits) { - return hash(theResourceType, theParamName, theUnits); + public static long calculateHashUnits(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUnits) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java index 714c2cd6f98..2cf23b8abe9 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.StringParam; @@ -27,9 +29,14 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.hibernate.search.annotations.*; +import org.hibernate.search.annotations.Analyze; +import org.hibernate.search.annotations.Analyzer; +import org.hibernate.search.annotations.ContainedIn; +import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Fields; +import org.hibernate.search.annotations.Indexed; +import org.hibernate.search.annotations.Store; -import javax.persistence.Index; import javax.persistence.*; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -55,50 +62,6 @@ import static org.apache.commons.lang3.StringUtils.left; @Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID") }) @Indexed() -//@AnalyzerDefs({ -// @AnalyzerDef(name = "autocompleteEdgeAnalyzer", -// tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= { -// @Parameter(name="pattern", value="(.*)"), -// @Parameter(name="group", value="1") -// }), -// filters = { -// @TokenFilterDef(factory = LowerCaseFilterFactory.class), -// @TokenFilterDef(factory = StopFilterFactory.class), -// @TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = { -// @Parameter(name = "minGramSize", value = "3"), -// @Parameter(name = "maxGramSize", value = "50") -// }), -// }), -// @AnalyzerDef(name = "autocompletePhoneticAnalyzer", -// tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class), -// filters = { -// @TokenFilterDef(factory=StandardFilterFactory.class), -// @TokenFilterDef(factory=StopFilterFactory.class), -// @TokenFilterDef(factory=PhoneticFilterFactory.class, params = { -// @Parameter(name="encoder", value="DoubleMetaphone") -// }), -// @TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = { -// @Parameter(name="language", value="English") -// }) -// }), -// @AnalyzerDef(name = "autocompleteNGramAnalyzer", -// tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), -// filters = { -// @TokenFilterDef(factory = WordDelimiterFilterFactory.class), -// @TokenFilterDef(factory = LowerCaseFilterFactory.class), -// @TokenFilterDef(factory = NGramFilterFactory.class, params = { -// @Parameter(name = "minGramSize", value = "3"), -// @Parameter(name = "maxGramSize", value = "20") -// }), -// }), -// @AnalyzerDef(name = "standardAnalyzer", -// tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), -// filters = { -// @TokenFilterDef(factory = LowerCaseFilterFactory.class), -// }) // Def -// } -//) -//@formatter:on public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { /* @@ -151,7 +114,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP super(); } - public ResourceIndexedSearchParamString(ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) { + public ResourceIndexedSearchParamString(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized, String theValueExact) { + setPartitionSettings(thePartitionSettings); setModelConfig(theModelConfig); setResourceType(theResourceType); setParamName(theParamName); @@ -159,22 +123,29 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP setValueExact(theValueExact); } - public void setHashIdentity(Long theHashIdentity) { - myHashIdentity = theHashIdentity; + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamString source = (ResourceIndexedSearchParamString) theSource; + myValueExact = source.myValueExact; + myValueNormalized = source.myValueNormalized; + myHashExact = source.myHashExact; + myHashIdentity = source.myHashIdentity; + myHashNormalizedPrefix = source.myHashNormalizedPrefix; } @Override @PrePersist @PreUpdate public void calculateHashes() { - if ((myHashIdentity == null || myHashNormalizedPrefix == null || myHashExact == null) && myModelConfig != null) { + if ((myHashIdentity == null || myHashNormalizedPrefix == null || myHashExact == null) && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); String valueNormalized = getValueNormalized(); String valueExact = getValueExact(); - setHashNormalizedPrefix(calculateHashNormalized(myModelConfig, resourceType, paramName, valueNormalized)); - setHashExact(calculateHashExact(resourceType, paramName, valueExact)); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); + setHashNormalizedPrefix(calculateHashNormalized(getPartitionSettings(), getPartitionId(), myModelConfig, resourceType, paramName, valueNormalized)); + setHashExact(calculateHashExact(getPartitionSettings(), getPartitionId(), resourceType, paramName, valueExact)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); } } @@ -182,6 +153,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP protected void clearHashes() { myHashNormalizedPrefix = null; myHashExact = null; + myHashIdentity = null; } @Override @@ -211,6 +183,10 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP return myHashIdentity; } + public void setHashIdentity(Long theHashIdentity) { + myHashIdentity = theHashIdentity; + } + public Long getHashExact() { calculateHashes(); return myHashExact; @@ -236,7 +212,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP @Override public void setId(Long theId) { - myId =theId; + myId = theId; } @@ -293,11 +269,21 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP return b.build(); } - public static long calculateHashExact(String theResourceType, String theParamName, String theValueExact) { - return hash(theResourceType, theParamName, theValueExact); + @Override + public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) { + if (!(theParam instanceof StringParam)) { + return false; + } + StringParam string = (StringParam) theParam; + String normalizedString = StringNormalizer.normalizeString(defaultString(string.getValue())); + return defaultString(getValueNormalized()).startsWith(normalizedString); } - public static long calculateHashNormalized(ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) { + public static long calculateHashExact(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValueExact) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theValueExact); + } + + public static long calculateHashNormalized(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, ModelConfig theModelConfig, String theResourceType, String theParamName, String theValueNormalized) { /* * If we're not allowing contained searches, we'll add the first * bit of the normalized value to the hash. This helps to @@ -309,17 +295,6 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP hashPrefixLength = 0; } - long hash = hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength)); - return hash; - } - - @Override - public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) { - if (!(theParam instanceof StringParam)) { - return false; - } - StringParam string = (StringParam)theParam; - String normalizedString = StringNormalizer.normalizeString(defaultString(string.getValue())); - return defaultString(getValueNormalized()).startsWith(normalizedString); + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, left(theValueNormalized, hashPrefixLength)); } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java index f18c2d53157..ba9b49d07e6 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.TokenParam; import org.apache.commons.lang3.StringUtils; @@ -101,26 +103,41 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa /** * Constructor */ - public ResourceIndexedSearchParamToken(String theResourceType, String theParamName, String theSystem, String theValue) { + public ResourceIndexedSearchParamToken(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, String theSystem, String theValue) { super(); + setPartitionSettings(thePartitionSettings); setResourceType(theResourceType); setParamName(theParamName); setSystem(theSystem); setValue(theValue); } + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamToken source = (ResourceIndexedSearchParamToken) theSource; + + mySystem = source.mySystem; + myValue = source.myValue; + myHashSystem = source.myHashSystem; + myHashSystemAndValue = source.getHashSystemAndValue(); + myHashValue = source.myHashValue; + myHashIdentity = source.myHashIdentity; + } + + @Override @PrePersist public void calculateHashes() { - if (myHashSystem == null) { + if (myHashSystem == null && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); String system = getSystem(); String value = getValue(); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); - setHashSystem(calculateHashSystem(resourceType, paramName, system)); - setHashSystemAndValue(calculateHashSystemAndValue(resourceType, paramName, system, value)); - setHashValue(calculateHashValue(resourceType, paramName, value)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); + setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system)); + setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value)); + setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value)); } } @@ -190,7 +207,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa @Override public void setId(Long theId) { - myId =theId; + myId = theId; } public String getSystem() { @@ -244,39 +261,39 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa return false; } TokenParam token = (TokenParam) theParam; - boolean retval = false; + boolean retVal = false; String valueString = defaultString(getValue()); String tokenValueString = defaultString(token.getValue()); // Only match on system if it wasn't specified if (token.getSystem() == null || token.getSystem().isEmpty()) { if (valueString.equalsIgnoreCase(tokenValueString)) { - retval = true; + retVal = true; } } else if (tokenValueString == null || tokenValueString.isEmpty()) { if (token.getSystem().equalsIgnoreCase(getSystem())) { - retval = true; + retVal = true; } } else { if (token.getSystem().equalsIgnoreCase(getSystem()) && valueString.equalsIgnoreCase(tokenValueString)) { - retval = true; + retVal = true; } } - return retval; + return retVal; } - public static long calculateHashSystem(String theResourceType, String theParamName, String theSystem) { - return hash(theResourceType, theParamName, trim(theSystem)); + public static long calculateHashSystem(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, trim(theSystem)); } - public static long calculateHashSystemAndValue(String theResourceType, String theParamName, String theSystem, String theValue) { - return hash(theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue)); + public static long calculateHashSystemAndValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem, String theValue) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, defaultString(trim(theSystem)), trim(theValue)); } - public static long calculateHashValue(String theResourceType, String theParamName, String theValue) { + public static long calculateHashValue(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theValue) { String value = trim(theValue); - return hash(theResourceType, theParamName, value); + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java index 6247dc668d1..29fc1ff91fb 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUri.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.UriParam; import org.apache.commons.lang3.StringUtils; @@ -80,21 +82,32 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara /** * Constructor */ - public ResourceIndexedSearchParamUri(String theResourceType, String theParamName, String theUri) { + public ResourceIndexedSearchParamUri(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, String theUri) { + setPartitionSettings(thePartitionSettings); setResourceType(theResourceType); setParamName(theParamName); setUri(theUri); } + @Override + public void copyMutableValuesFrom(T theSource) { + super.copyMutableValuesFrom(theSource); + ResourceIndexedSearchParamUri source = (ResourceIndexedSearchParamUri) theSource; + myUri = source.myUri; + myHashUri = source.myHashUri; + myHashIdentity = source.myHashIdentity; + } + + @Override @PrePersist public void calculateHashes() { - if (myHashUri == null) { + if (myHashUri == null && getParamName() != null) { String resourceType = getResourceType(); String paramName = getParamName(); String uri = getUri(); - setHashIdentity(calculateHashIdentity(resourceType, paramName)); - setHashUri(calculateHashUri(resourceType, paramName, uri)); + setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); + setHashUri(calculateHashUri(getPartitionSettings(), getPartitionId(), resourceType, paramName, uri)); } } @@ -149,7 +162,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara @Override public void setId(Long theId) { - myId =theId; + myId = theId; } @@ -196,8 +209,8 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara return defaultString(getUri()).equalsIgnoreCase(uri.getValueNotNull()); } - public static long calculateHashUri(String theResourceType, String theParamName, String theUri) { - return hash(theResourceType, theParamName, theUri); + public static long calculateHashUri(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, String theUri) { + return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUri); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java index e7e627c035c..0acddbc609b 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceLink.java @@ -55,49 +55,43 @@ public class ResourceLink extends BaseResourceIndex { @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false) private Long mySourceResourcePid; - @Column(name = "SOURCE_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN) + @Column(name = "SOURCE_RESOURCE_TYPE", updatable = false, nullable = false, length = ResourceTable.RESTYPE_LEN) @Field() private String mySourceResourceType; @ManyToOne(optional = true, fetch = FetchType.LAZY) - @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = true, foreignKey = @ForeignKey(name = "FK_RESLINK_TARGET")) + @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = true, insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_RESLINK_TARGET")) private ResourceTable myTargetResource; - @Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = true) + @Column(name = "TARGET_RESOURCE_ID", insertable = true, updatable = true, nullable = true) @Field() private Long myTargetResourcePid; - @Column(name = "TARGET_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN) @Field() private String myTargetResourceType; - @Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true) @Field() private String myTargetResourceUrl; - @Field() @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3 @Temporal(TemporalType.TIMESTAMP) private Date myUpdated; + @Transient + private transient String myTargetResourceId; public ResourceLink() { super(); } - public ResourceLink(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) { - super(); - setSourcePath(theSourcePath); - setSourceResource(theSourceResource); - setTargetResourceUrl(theTargetResourceUrl); - setUpdated(theUpdated); + public String getTargetResourceId() { + if (myTargetResourceId == null && myTargetResource != null) { + myTargetResourceId = getTargetResource().getIdDt().getIdPart(); + } + return myTargetResourceId; } - public ResourceLink(String theSourcePath, ResourceTable theSourceResource, ResourceTable theTargetResource, Date theUpdated) { - super(); - setSourcePath(theSourcePath); - setSourceResource(theSourceResource); - setTargetResource(theTargetResource); - setUpdated(theUpdated); + public String getTargetResourceType() { + return myTargetResourceType; } @Override @@ -115,11 +109,22 @@ public class ResourceLink extends BaseResourceIndex { EqualsBuilder b = new EqualsBuilder(); b.append(mySourcePath, obj.mySourcePath); b.append(mySourceResource, obj.mySourceResource); - b.append(myTargetResourcePid, obj.myTargetResourcePid); b.append(myTargetResourceUrl, obj.myTargetResourceUrl); + b.append(myTargetResourceType, obj.myTargetResourceType); + b.append(getTargetResourceId(), obj.getTargetResourceId()); return b.isEquals(); } + @Override + public void copyMutableValuesFrom(T theSource) { + ResourceLink source = (ResourceLink) theSource; + myTargetResource = source.getTargetResource(); + myTargetResourceId = source.getTargetResourceId(); + myTargetResourcePid = source.getTargetResourcePid(); + myTargetResourceType = source.getTargetResourceType(); + myTargetResourceUrl = source.getTargetResourceUrl(); + } + public String getSourcePath() { return mySourcePath; } @@ -128,6 +133,10 @@ public class ResourceLink extends BaseResourceIndex { mySourcePath = theSourcePath; } + public Long getSourceResourcePid() { + return mySourceResourcePid; + } + public ResourceTable getSourceResource() { return mySourceResource; } @@ -138,15 +147,16 @@ public class ResourceLink extends BaseResourceIndex { mySourceResourceType = theSourceResource.getResourceType(); } - public ResourceTable getTargetResource() { - return myTargetResource; + public void setTargetResource(String theResourceType, Long theResourcePid, String theTargetResourceId) { + Validate.notBlank(theResourceType); + + myTargetResourceType = theResourceType; + myTargetResourcePid = theResourcePid; + myTargetResourceId = theTargetResourceId; } - public void setTargetResource(ResourceTable theTargetResource) { - Validate.notNull(theTargetResource); - myTargetResource = theTargetResource; - myTargetResourcePid = theTargetResource.getId(); - myTargetResourceType = theTargetResource.getResourceType(); + public String getTargetResourceUrl() { + return myTargetResourceUrl; } public Long getTargetResourcePid() { @@ -158,18 +168,25 @@ public class ResourceLink extends BaseResourceIndex { Validate.isTrue(theTargetResourceUrl.hasResourceType()); // if (theTargetResourceUrl.hasIdPart()) { - // do nothing + // do nothing // } else { - // Must have set an url like http://example.org/something - // We treat 'something' as the resource type because of fix for #659. Prior to #659 fix, 'something' was - // treated as the id and 'example.org' was treated as the resource type - // TODO: log a warning? + // Must have set an url like http://example.org/something + // We treat 'something' as the resource type because of fix for #659. Prior to #659 fix, 'something' was + // treated as the id and 'example.org' was treated as the resource type + // TODO: log a warning? // } myTargetResourceType = theTargetResourceUrl.getResourceType(); myTargetResourceUrl = theTargetResourceUrl.getValue(); } + public void setTargetResourceUrlCanonical(String theTargetResourceUrl) { + Validate.notBlank(theTargetResourceUrl); + + myTargetResourceType = "(unknown)"; + myTargetResourceUrl = theTargetResourceUrl; + } + public Date getUpdated() { return myUpdated; } @@ -198,8 +215,9 @@ public class ResourceLink extends BaseResourceIndex { HashCodeBuilder b = new HashCodeBuilder(); b.append(mySourcePath); b.append(mySourceResource); - b.append(myTargetResourcePid); b.append(myTargetResourceUrl); + b.append(getTargetResourceType()); + b.append(getTargetResourceId()); return b.toHashCode(); } @@ -210,10 +228,45 @@ public class ResourceLink extends BaseResourceIndex { b.append("path=").append(mySourcePath); b.append(", src=").append(mySourceResourcePid); b.append(", target=").append(myTargetResourcePid); + b.append(", targetType=").append(myTargetResourceType); b.append(", targetUrl=").append(myTargetResourceUrl); b.append("]"); return b.toString(); } + public ResourceTable getTargetResource() { + return myTargetResource; + } + + public static ResourceLink forAbsoluteReference(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) { + ResourceLink retVal = new ResourceLink(); + retVal.setSourcePath(theSourcePath); + retVal.setSourceResource(theSourceResource); + retVal.setTargetResourceUrl(theTargetResourceUrl); + retVal.setUpdated(theUpdated); + return retVal; + } + + /** + * Factory for canonical URL + */ + public static ResourceLink forLogicalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceUrl, Date theUpdated) { + ResourceLink retVal = new ResourceLink(); + retVal.setSourcePath(theSourcePath); + retVal.setSourceResource(theSourceResource); + retVal.setTargetResourceUrlCanonical(theTargetResourceUrl); + retVal.setUpdated(theUpdated); + return retVal; + } + + public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated) { + ResourceLink retVal = new ResourceLink(); + retVal.setSourcePath(theSourcePath); + retVal.setSourceResource(theSourceResource); + retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId); + retVal.setUpdated(theUpdated); + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java index 10d7189410a..cedd46639a8 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity; */ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.IndexNonDeletedInterceptor; import ca.uhn.fhir.model.primitive.IdDt; @@ -29,12 +30,20 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hibernate.annotations.OptimisticLock; -import org.hibernate.search.annotations.*; +import org.hibernate.search.annotations.Analyze; +import org.hibernate.search.annotations.Analyzer; +import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Fields; +import org.hibernate.search.annotations.Indexed; +import org.hibernate.search.annotations.Store; -import javax.persistence.Index; import javax.persistence.*; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -44,14 +53,12 @@ import static org.apache.commons.lang3.StringUtils.defaultString; @Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = { @Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"), @Index(name = "IDX_RES_LANG", columnList = "RES_TYPE,RES_LANGUAGE"), - @Index(name = "IDX_RES_PROFILE", columnList = "RES_PROFILE"), @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"), @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") }) -public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource { +public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup { public static final int RESTYPE_LEN = 40; private static final int MAX_LANGUAGE_LENGTH = 20; - private static final int MAX_PROFILE_LENGTH = 200; private static final long serialVersionUID = 1L; /** @@ -158,10 +165,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas @OptimisticLock(excluded = true) private boolean myParamsUriPopulated; - @Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true) - @OptimisticLock(excluded = true) - private String myProfile; - // Added in 3.0.0 - Should make this a primitive Boolean at some point @OptimisticLock(excluded = true) @Column(name = "SP_CMPSTR_UNIQ_PRESENT") @@ -182,9 +185,9 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas * {@link #myHasLinks} is true, meaning that there are actually resource links present * right now. This avoids Hibernate Search triggering a select on the resource link * table. - * + *

        * This field is used by FulltextSearchSvcImpl - * + *

        * You can test that any changes don't cause extra queries by running * FhirResourceDaoR4QueryCountTest */ @@ -225,6 +228,13 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas @OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource") private ForcedId myForcedId; + /** + * Constructor + */ + public ResourceTable() { + super(); + } + @Override public ResourceTag addTag(TagDefinition theTag) { for (ResourceTag next : getTags()) { @@ -232,7 +242,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas return next; } } - ResourceTag tag = new ResourceTag(this, theTag); + ResourceTag tag = new ResourceTag(this, theTag, getPartitionId()); getTags().add(tag); return tag; } @@ -386,17 +396,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas getParamsUri().addAll(theParamsUri); } - public String getProfile() { - return myProfile; - } - - public void setProfile(String theProfile) { - if (defaultString(theProfile).length() > MAX_PROFILE_LENGTH) { - throw new UnprocessableEntityException("Profile name exceeds maximum length of " + MAX_PROFILE_LENGTH + " chars: " + theProfile); - } - myProfile = theProfile; - } - @Override public Long getResourceId() { return getId(); @@ -556,6 +555,8 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas retVal.setFhirVersion(getFhirVersion()); retVal.setDeleted(getDeleted()); retVal.setResourceTable(this); + retVal.setForcedId(getForcedId()); + retVal.setPartitionId(getPartitionId()); retVal.getTags().clear(); @@ -626,7 +627,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas @Override public IdDt getIdDt() { if (getForcedId() == null) { - Long id = getResourceId(); + Long id = this.getResourceId(); return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); } else { // Avoid a join query if possible diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java index 9fcb5200ce4..d255c5d0005 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -28,8 +29,8 @@ import org.apache.commons.lang3.builder.ToStringStyle; import javax.persistence.*; @Entity -@Table(name = "HFJ_RES_TAG", uniqueConstraints= { - @UniqueConstraint(name="IDX_RESTAG_TAGID", columnNames= {"RES_ID","TAG_ID"}) +@Table(name = "HFJ_RES_TAG", uniqueConstraints = { + @UniqueConstraint(name = "IDX_RESTAG_TAGID", columnNames = {"RES_ID", "TAG_ID"}) }) public class ResourceTag extends BaseTag { @@ -42,7 +43,7 @@ public class ResourceTag extends BaseTag { private Long myId; @ManyToOne(cascade = {}) - @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey=@ForeignKey(name="FK_RESTAG_RESOURCE")) + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE")) private ResourceTable myResource; @Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN, nullable = false) @@ -51,6 +52,17 @@ public class ResourceTag extends BaseTag { @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourceId; + public ResourceTag() { + } + + public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag, RequestPartitionId theRequestPartitionId) { + setTag(theTag); + setResource(theResourceTable); + setResourceId(theResourceTable.getId()); + setResourceType(theResourceTable.getResourceType()); + setPartitionId(theRequestPartitionId); + } + public Long getResourceId() { return myResourceId; } @@ -59,28 +71,18 @@ public class ResourceTag extends BaseTag { myResourceId = theResourceId; } - public ResourceTag() { - } - - public ResourceTag(ResourceTable theResourceTable, TagDefinition theTag) { - setTag(theTag); - setResource(theResourceTable); - setResourceId(theResourceTable.getId()); - setResourceType(theResourceTable.getResourceType()); - } - public ResourceTable getResource() { return myResource; } - public String getResourceType() { - return myResourceType; - } - public void setResource(ResourceTable theResource) { myResource = theResource; } + public String getResourceType() { + return myResourceType; + } + public void setResourceType(String theResourceType) { myResourceType = theResourceType; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresent.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresent.java index fea07faaf2c..f29b3d9bfc9 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresent.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/SearchParamPresent.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -32,7 +34,7 @@ import java.io.Serializable; @Index(name = "IDX_RESPARMPRESENT_RESID", columnList = "RES_ID"), @Index(name = "IDX_RESPARMPRESENT_HASHPRES", columnList = "HASH_PRESENCE") }) -public class SearchParamPresent implements Serializable { +public class SearchParamPresent extends BasePartitionable implements Serializable { private static final long serialVersionUID = 1L; @@ -46,12 +48,14 @@ public class SearchParamPresent implements Serializable { @ManyToOne() @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_RESPARMPRES_RESID")) private ResourceTable myResource; - @Column(name="RES_ID", nullable = false, insertable = false, updatable = false) + @Column(name = "RES_ID", nullable = false, insertable = false, updatable = false) private Long myResourcePid; @Transient private transient String myParamName; @Column(name = "HASH_PRESENCE") private Long myHashPresence; + @Transient + private transient PartitionSettings myPartitionSettings; /** * Constructor @@ -63,11 +67,11 @@ public class SearchParamPresent implements Serializable { @SuppressWarnings("unused") @PrePersist public void calculateHashes() { - if (myHashPresence == null) { + if (myHashPresence == null && getParamName() != null) { String resourceType = getResource().getResourceType(); String paramName = getParamName(); boolean present = myPresent; - setHashPresence(calculateHashPresence(resourceType, paramName, present)); + setHashPresence(calculateHashPresence(getPartitionSettings(), getPartitionId(), resourceType, paramName, present)); } } @@ -110,12 +114,21 @@ public class SearchParamPresent implements Serializable { b.append("resPid", myResource.getIdDt().toUnqualifiedVersionless().getValue()); b.append("paramName", myParamName); b.append("present", myPresent); + b.append("partition", getPartitionId()); return b.build(); } - public static long calculateHashPresence(String theResourceType, String theParamName, Boolean thePresent) { + public PartitionSettings getPartitionSettings() { + return myPartitionSettings; + } + + public void setPartitionSettings(PartitionSettings thePartitionSettings) { + myPartitionSettings = thePartitionSettings; + } + + public static long calculateHashPresence(PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName, Boolean thePresent) { String string = thePresent != null ? Boolean.toString(thePresent) : Boolean.toString(false); - return BaseResourceIndexedSearchParam.hash(theResourceType, theParamName, string); + return BaseResourceIndexedSearchParam.hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, string); } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 66f2c5b17ed..e93be6d91dd 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -25,38 +25,30 @@ import ca.uhn.fhir.rest.api.Constants; public class JpaConstants { /** - * Non-instantiable + * Userdata key for tracking the fact that a resource ID was assigned by the server */ - private JpaConstants() { - // nothing - } - + public static final String RESOURCE_ID_SERVER_ASSIGNED = JpaConstants.class.getName() + "_RESOURCE_ID_SERVER_ASSIGNED"; /** * Operation name for the $apply-codesystem-delta-add operation */ public static final String OPERATION_APPLY_CODESYSTEM_DELTA_ADD = "$apply-codesystem-delta-add"; - /** * Operation name for the $apply-codesystem-delta-remove operation */ public static final String OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE = "$apply-codesystem-delta-remove"; - /** * Operation name for the $expunge operation */ public static final String OPERATION_EXPUNGE = "$expunge"; - /** * Operation name for the $match operation */ public static final String OPERATION_MATCH = "$match"; - /** * @deprecated Replace with {@link #OPERATION_EXPUNGE} */ @Deprecated public static final String OPERATION_NAME_EXPUNGE = OPERATION_EXPUNGE; - /** * Parameter name for the $expunge operation */ @@ -84,113 +76,91 @@ public class JpaConstants { * be removed if they are nt explicitly included in updates */ public static final String HEADER_META_SNAPSHOT_MODE = "X-Meta-Snapshot-Mode"; - /** * Operation name for the $lookup operation */ public static final String OPERATION_LOOKUP = "$lookup"; - /** * Operation name for the $expand operation */ public static final String OPERATION_EXPAND = "$expand"; - /** * Operation name for the $validate-code operation */ public static final String OPERATION_VALIDATE_CODE = "$validate-code"; - /** * Operation name for the $get-resource-counts operation */ public static final String OPERATION_GET_RESOURCE_COUNTS = "$get-resource-counts"; - /** * Operation name for the $meta operation */ public static final String OPERATION_META = "$meta"; - /** * Operation name for the $validate operation */ // NB don't delete this, it's used in Smile as well, even though hapi-fhir-server uses the version from Constants.java public static final String OPERATION_VALIDATE = Constants.EXTOP_VALIDATE; - /** * Operation name for the $suggest-keywords operation */ public static final String OPERATION_SUGGEST_KEYWORDS = "$suggest-keywords"; - /** * Operation name for the $everything operation */ public static final String OPERATION_EVERYTHING = "$everything"; - /** * Operation name for the $process-message operation */ public static final String OPERATION_PROCESS_MESSAGE = "$process-message"; - /** * Operation name for the $meta-delete operation */ public static final String OPERATION_META_DELETE = "$meta-delete"; - /** * Operation name for the $meta-add operation */ public static final String OPERATION_META_ADD = "$meta-add"; - /** * Operation name for the $translate operation */ public static final String OPERATION_TRANSLATE = "$translate"; - /** * Operation name for the $document operation */ public static final String OPERATION_DOCUMENT = "$document"; - /** * Trigger a subscription manually for a given resource */ public static final String OPERATION_TRIGGER_SUBSCRIPTION = "$trigger-subscription"; - /** * Operation name for the "$subsumes" operation */ public static final String OPERATION_SUBSUMES = "$subsumes"; - /** * Operation name for the "$snapshot" operation */ public static final String OPERATION_SNAPSHOT = "$snapshot"; - /** * Operation name for the "$binary-access" operation */ public static final String OPERATION_BINARY_ACCESS_READ = "$binary-access-read"; - /** * Operation name for the "$binary-access" operation */ public static final String OPERATION_BINARY_ACCESS_WRITE = "$binary-access-write"; - /** * Operation name for the "$upload-external-code-system" operation */ public static final String OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM = "$upload-external-code-system"; - /** * Operation name for the "$export" operation */ public static final String OPERATION_EXPORT = "$export"; - /** * Operation name for the "$export-poll-status" operation */ public static final String OPERATION_EXPORT_POLL_STATUS = "$export-poll-status"; - /** *

        * This extension should be of type string and should be @@ -198,7 +168,6 @@ public class JpaConstants { *

        */ public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template"; - /** * This extension URL indicates whether a REST HOOK delivery should * include the version ID when delivering. @@ -208,7 +177,6 @@ public class JpaConstants { *

        */ public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids"; - /** * This extension URL indicates whether a REST HOOK delivery should * reload the resource and deliver the latest version always. This @@ -226,12 +194,10 @@ public class JpaConstants { *

        */ public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version"; - /** * Indicate which strategy will be used to match this subscription */ public static final String EXT_SUBSCRIPTION_MATCHING_STRATEGY = "http://hapifhir.io/fhir/StructureDefinition/subscription-matching-strategy"; - /** *

        * This extension should be of type string and should be @@ -239,45 +205,43 @@ public class JpaConstants { *

        */ public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from"; - /** * Extension ID for external binary references */ public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id"; - /** * Placed in system-generated extensions */ public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED"; - /** * Message added to expansion valueset */ public static final String EXT_VALUESET_EXPANSION_MESSAGE = "http://hapifhir.io/fhir/StructureDefinition/valueset-expansion-message"; - - /** * Parameter for the $export operation */ public static final String PARAM_EXPORT_POLL_STATUS_JOB_ID = "_jobId"; - /** * Parameter for the $export operation */ public static final String PARAM_EXPORT_OUTPUT_FORMAT = "_outputFormat"; - /** * Parameter for the $export operation */ public static final String PARAM_EXPORT_TYPE = "_type"; - /** * Parameter for the $export operation */ public static final String PARAM_EXPORT_SINCE = "_since"; - /** * Parameter for the $export operation */ public static final String PARAM_EXPORT_TYPE_FILTER = "_typeFilter"; + + /** + * Non-instantiable + */ + private JpaConstants() { + // nothing + } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java new file mode 100644 index 00000000000..4f524f07cea --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java @@ -0,0 +1,51 @@ +package ca.uhn.fhir.jpa.model.util; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2020 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 class ProviderConstants { + public static final String SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID = "resourceId"; + public static final String SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL = "searchUrl"; + + /** + * Operation name: add partition + */ + public static final String PARTITION_MANAGEMENT_ADD_PARTITION = "$partition-management-add-partition"; + + /** + * Operation name: update partition + */ + public static final String PARTITION_MANAGEMENT_UPDATE_PARTITION = "$partition-management-update-partition"; + + /** + * Operation name: update partition + */ + public static final String PARTITION_MANAGEMENT_DELETE_PARTITION = "$partition-management-delete-partition"; + + /** + * Operation name: read partition + */ + public static final String PARTITION_MANAGEMENT_READ_PARTITION = "$partition-management-read-partition"; + + public static final String PARTITION_MANAGEMENT_PARTITION_ID = "id"; + public static final String PARTITION_MANAGEMENT_PARTITION_NAME = "name"; + public static final String PARTITION_MANAGEMENT_PARTITION_DESC = "description"; + +} diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/any/AnyListResourceTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/any/AnyListResourceTest.java deleted file mode 100644 index 46b71084710..00000000000 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/any/AnyListResourceTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package ca.uhn.fhir.jpa.model.any; - -import org.hl7.fhir.r5.model.ListResource; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class AnyListResourceTest { - @Test - public void getCodeFirstRep() { - AnyListResource listResource = AnyListResource.fromResource(new ListResource()); - listResource.addCode("foo", "bar"); - assertEquals("foo", listResource.getCodeFirstRep().getSystem()); - assertEquals("bar", listResource.getCodeFirstRep().getValue()); - } - - @Test - public void getIdentifierFirstRep() { - AnyListResource listResource = AnyListResource.fromResource(new ListResource()); - listResource.addIdentifier("foo", "bar"); - assertEquals("foo", listResource.getIdentifierirstRep().getSystem()); - assertEquals("bar", listResource.getIdentifierirstRep().getValue()); - } -} diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java index 7cf602f5420..0e3865e2038 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamCoordsTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -12,10 +13,12 @@ public class ResourceIndexedSearchParamCoordsTest { ResourceIndexedSearchParamCoords val1 = new ResourceIndexedSearchParamCoords() .setLatitude(100) .setLongitude(10); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamCoords val2 = new ResourceIndexedSearchParamCoords() .setLatitude(100) .setLongitude(10); + val2.setPartitionSettings(new PartitionSettings()); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java index 4ba8d5517e5..74fac849060 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Before; import org.junit.Test; @@ -36,8 +37,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsTrueForMatchingNullDates() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "SomeResource", null, null, null, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", null, null, null, null, "SomeValue"); assertTrue(param.equals(param2)); assertTrue(param2.equals(param)); @@ -46,8 +47,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsTrueForMatchingDates() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, null, date2B, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1B, null, date2B, null, "SomeValue"); assertTrue(param.equals(param2)); assertTrue(param2.equals(param)); @@ -56,8 +57,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsTrueForMatchingTimeStampsThatMatch() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, null, timestamp2B, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp1B, null, timestamp2B, null, "SomeValue"); assertTrue(param.equals(param2)); assertTrue(param2.equals(param)); @@ -68,8 +69,8 @@ public class ResourceIndexedSearchParamDateTest { // other will be equivalent but will be a java.sql.Timestamp. Equals should work in both directions. @Test public void equalsIsTrueForMixedTimestampsAndDates() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); assertTrue(param.equals(param2)); assertTrue(param2.equals(param)); @@ -78,8 +79,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsFalseForNonMatchingDates() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, null, date1A, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date2A, null, date1A, null, "SomeValue"); assertFalse(param.equals(param2)); assertFalse(param2.equals(param)); @@ -88,8 +89,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsFalseForNonMatchingDatesNullCase() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", null, null, null, null, "SomeValue"); assertFalse(param.equals(param2)); assertFalse(param2.equals(param)); @@ -98,8 +99,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsFalseForNonMatchingTimeStamps() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp1A, null, timestamp2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue"); assertFalse(param.equals(param2)); assertFalse(param2.equals(param)); @@ -108,8 +109,8 @@ public class ResourceIndexedSearchParamDateTest { @Test public void equalsIsFalseForMixedTimestampsAndDatesThatDoNotMatch() { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); - ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue"); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", date1A, null, date2A, null, "SomeValue"); + ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate(new PartitionSettings(),"Patient", "SomeResource", timestamp2A, null, timestamp1A, null, "SomeValue"); assertFalse(param.equals(param2)); assertFalse(param2.equals(param)); @@ -122,10 +123,12 @@ public class ResourceIndexedSearchParamDateTest { ResourceIndexedSearchParamDate val1 = new ResourceIndexedSearchParamDate() .setValueHigh(new Date(100000000L)) .setValueLow(new Date(111111111L)); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamDate val2 = new ResourceIndexedSearchParamDate() .setValueHigh(new Date(100000000L)) .setValueLow(new Date(111111111L)); + val2.setPartitionSettings(new PartitionSettings()); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java index 3d98940891e..8c20f544833 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Test; import java.math.BigDecimal; @@ -10,7 +11,7 @@ import static org.junit.Assert.assertNotEquals; public class ResourceIndexedSearchParamQuantityTest { private ResourceIndexedSearchParamQuantity createParam(String theParamName, String theValue, String theSystem, String theUnits) { - ResourceIndexedSearchParamQuantity token = new ResourceIndexedSearchParamQuantity("Patient", theParamName, new BigDecimal(theValue), theSystem, theUnits); + ResourceIndexedSearchParamQuantity token = new ResourceIndexedSearchParamQuantity(new PartitionSettings(), "Patient", theParamName, new BigDecimal(theValue), theSystem, theUnits); token.setResource(new ResourceTable().setResourceType("Patient")); return token; } @@ -29,9 +30,11 @@ public class ResourceIndexedSearchParamQuantityTest { public void testEquals() { ResourceIndexedSearchParamQuantity val1 = new ResourceIndexedSearchParamQuantity() .setValue(new BigDecimal(123)); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamQuantity val2 = new ResourceIndexedSearchParamQuantity() .setValue(new BigDecimal(123)); + val2.setPartitionSettings(new PartitionSettings()); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java index 2f765664ec5..ff07d7c6999 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamStringTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -10,7 +11,7 @@ public class ResourceIndexedSearchParamStringTest { @Test public void testHashFunctions() { - ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new ModelConfig(), "Patient", "NAME", "value", "VALUE"); + ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionSettings(), new ModelConfig(), "Patient", "NAME", "value", "VALUE"); token.setResource(new ResourceTable().setResourceType("Patient")); // Make sure our hashing function gives consistent results @@ -20,7 +21,7 @@ public class ResourceIndexedSearchParamStringTest { @Test public void testHashFunctionsPrefixOnly() { - ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new ModelConfig(), "Patient", "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ"); + ResourceIndexedSearchParamString token = new ResourceIndexedSearchParamString(new PartitionSettings(), new ModelConfig(), "Patient", "NAME", "vZZZZZZZZZZZZZZZZ", "VZZZZZZzzzZzzzZ"); token.setResource(new ResourceTable().setResourceType("Patient")); // Should be the same as in testHashFunctions() @@ -36,10 +37,30 @@ public class ResourceIndexedSearchParamStringTest { ResourceIndexedSearchParamString val1 = new ResourceIndexedSearchParamString() .setValueExact("aaa") .setValueNormalized("AAA"); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamString val2 = new ResourceIndexedSearchParamString() .setValueExact("aaa") .setValueNormalized("AAA"); + val2.setPartitionSettings(new PartitionSettings()); + val2.calculateHashes(); + assertEquals(val1, val1); + assertEquals(val1, val2); + assertNotEquals(val1, null); + assertNotEquals(val1, ""); + } + + @Test + public void testEqualsDifferentPartition() { + ResourceIndexedSearchParamString val1 = new ResourceIndexedSearchParamString() + .setValueExact("aaa") + .setValueNormalized("AAA"); + val1.setPartitionSettings(new PartitionSettings().setIncludePartitionInSearchHashes(true)); + val1.calculateHashes(); + ResourceIndexedSearchParamString val2 = new ResourceIndexedSearchParamString() + .setValueExact("aaa") + .setValueNormalized("AAA"); + val2.setPartitionSettings(new PartitionSettings().setIncludePartitionInSearchHashes(true)); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java index f852c2d1f90..7bd2113deae 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamTokenTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -9,7 +10,7 @@ public class ResourceIndexedSearchParamTokenTest { @Test public void testHashFunctions() { - ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("Patient", "NAME", "SYSTEM", "VALUE"); + ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE"); token.setResource(new ResourceTable().setResourceType("Patient")); // Make sure our hashing function gives consistent results @@ -20,7 +21,7 @@ public class ResourceIndexedSearchParamTokenTest { @Test public void testHashFunctionsWithOverlapNames() { - ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken("Patient", "NAME", "SYSTEM", "VALUE"); + ResourceIndexedSearchParamToken token = new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "NAME", "SYSTEM", "VALUE"); token.setResource(new ResourceTable().setResourceType("Patient")); // Make sure our hashing function gives consistent results @@ -33,9 +34,11 @@ public class ResourceIndexedSearchParamTokenTest { public void testEquals() { ResourceIndexedSearchParamToken val1 = new ResourceIndexedSearchParamToken() .setValue("AAA"); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamToken val2 = new ResourceIndexedSearchParamToken() .setValue("AAA"); + val2.setPartitionSettings(new PartitionSettings()); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java index d3a248aceb4..1aadd00dfe4 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamUriTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.entity; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -9,7 +10,7 @@ public class ResourceIndexedSearchParamUriTest { @Test public void testHashFunctions() { - ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri("Patient", "NAME", "http://example.com"); + ResourceIndexedSearchParamUri token = new ResourceIndexedSearchParamUri(new PartitionSettings(), "Patient", "NAME", "http://example.com"); token.setResource(new ResourceTable().setResourceType("Patient")); // Make sure our hashing function gives consistent results @@ -20,9 +21,11 @@ public class ResourceIndexedSearchParamUriTest { public void testEquals() { ResourceIndexedSearchParamUri val1 = new ResourceIndexedSearchParamUri() .setUri("http://foo"); + val1.setPartitionSettings(new PartitionSettings()); val1.calculateHashes(); ResourceIndexedSearchParamUri val2 = new ResourceIndexedSearchParamUri() .setUri("http://foo"); + val2.setPartitionSettings(new PartitionSettings()); val2.calculateHashes(); assertEquals(val1, val1); assertEquals(val1, val2); diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 709f74dfc10..b9c013dda88 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -100,6 +100,22 @@ org.springframework.retry spring-retry + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.hibernate + hibernate-search-engine + + + org.jscience + jscience + org.quartz-scheduler diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java index fb77ea8af24..7b4e98ae21e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java @@ -44,7 +44,6 @@ import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -@Service public class MatchUrlService { @Autowired diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java index 958e58c2f1c..1cdb6f4ca37 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java @@ -15,10 +15,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.hl7.fhir.dstu3.model.Location; import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -456,45 +456,33 @@ public class SearchParameterMap implements Serializable { return b.toString(); } + public void clean() { for (Map.Entry>> nextParamEntry : this.entrySet()) { String nextParamName = nextParamEntry.getKey(); List> andOrParams = nextParamEntry.getValue(); - clean(nextParamName, andOrParams); + cleanParameter(nextParamName, andOrParams); } } /* - * Filter out + * Given a particular named parameter, e.g. `name`, iterate over AndOrParams and remove any which are empty. */ - private void clean(String theParamName, List> theAndOrParams) { - for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) { - List nextOrList = theAndOrParams.get(andListIdx); + private void cleanParameter(String theParamName, List> theAndOrParams) { + theAndOrParams + .forEach( + orList -> { + List emptyParameters = orList.stream() + .filter(nextOr -> nextOr.getMissing() == null) + .filter(nextOr -> nextOr instanceof QuantityParam) + .filter(nextOr -> isBlank(((QuantityParam) nextOr).getValueAsString())) + .collect(Collectors.toList()); - for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) { - IQueryParameterType nextOr = nextOrList.get(orListIdx); - boolean hasNoValue = false; - if (nextOr.getMissing() != null) { - continue; - } - if (nextOr instanceof QuantityParam) { - if (isBlank(((QuantityParam) nextOr).getValueAsString())) { - hasNoValue = true; - } - } - - if (hasNoValue) { ourLog.debug("Ignoring empty parameter: {}", theParamName); - nextOrList.remove(orListIdx); - orListIdx--; + orList.removeAll(emptyParameters); } - } - - if (nextOrList.isEmpty()) { - theAndOrParams.remove(andListIdx); - andListIdx--; - } - } + ); + theAndOrParams.removeIf(List::isEmpty); } public void setNearDistanceParam(QuantityParam theQuantityParam) { @@ -505,30 +493,6 @@ public class SearchParameterMap implements Serializable { return myNearDistanceParam; } - public void setLocationDistance() { - if (containsKey(Location.SP_NEAR_DISTANCE)) { - List> paramAndList = get(Location.SP_NEAR_DISTANCE); - - if (paramAndList.isEmpty()) { - return; - } - if (paramAndList.size() > 1) { - throw new IllegalArgumentException("Only one " + ca.uhn.fhir.model.dstu2.resource.Location.SP_NEAR_DISTANCE + " parameter may be present"); - } - List paramOrList = paramAndList.get(0); - if (paramOrList.isEmpty()) { - return; - } - if (paramOrList.size() > 1) { - throw new IllegalArgumentException("Only one " + ca.uhn.fhir.model.dstu2.resource.Location.SP_NEAR_DISTANCE + " parameter may be present"); - } - setNearDistanceParam((QuantityParam) paramOrList.get(0)); - - // Need to remove near-distance or it we'll get a hashcode predicate for it - remove(Location.SP_NEAR_DISTANCE); - } - } - public enum EverythingModeEnum { /* * Don't reorder! We rely on the ordinals diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java new file mode 100644 index 00000000000..88d2acc3e8f --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java @@ -0,0 +1,99 @@ +package ca.uhn.fhir.jpa.searchparam.config; + +/*- + * #%L + * HAPI FHIR Search Parameters + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher; +import ca.uhn.fhir.jpa.searchparam.matcher.IndexedSearchParamExtractor; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SearchParamConfig { + + @Autowired + private FhirContext myFhirContext; + + @Bean + public ISearchParamExtractor searchParamExtractor() { + switch (myFhirContext.getVersion().getVersion()) { + + case DSTU2: + return new SearchParamExtractorDstu2(); + case DSTU3: + return new SearchParamExtractorDstu3(); + case R4: + return new SearchParamExtractorR4(); + case R5: + return new SearchParamExtractorR5(); + case DSTU2_HL7ORG: + case DSTU2_1: + default: + throw new IllegalStateException("Can not handle version: " + myFhirContext.getVersion().getVersion()); + } + } + + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryImpl(); + } + + @Bean + public MatchUrlService matchUrlService() { + return new MatchUrlService(); + } + + @Bean + @Lazy + public SearchParamExtractorService searchParamExtractorService(){ + return new SearchParamExtractorService(); + } + + @Bean + public IndexedSearchParamExtractor indexedSearchParamExtractor() { + return new IndexedSearchParamExtractor(); + } + + @Bean + public InMemoryResourceMatcher InMemoryResourceMatcher() { + return new InMemoryResourceMatcher(); + } + + @Bean + public SearchParamMatcher SearchParamMatcher() { + return new SearchParamMatcher(); + } + +} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java deleted file mode 100644 index 489cecb0243..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java +++ /dev/null @@ -1,55 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.config; - -/*- - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; -import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -public class SearchParamDstu2Config extends BaseSearchParamConfig { - @Bean - @Primary - public FhirContext fhirContextDstu2() { - FhirContext retVal = FhirContext.forDstu2(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu2 searchParamExtractor() { - return new SearchParamExtractorDstu2(); - } - - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu2") - public IValidationSupport validationSupportChainDstu2() { - return new DefaultProfileValidationSupport(); - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java deleted file mode 100644 index 38b2eed2bb8..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java +++ /dev/null @@ -1,47 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.config; - -/*- - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -public class SearchParamDstu3Config extends BaseSearchParamConfig { - @Bean - @Primary - public FhirContext fhirContextDstu3() { - FhirContext retVal = FhirContext.forDstu3(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu3 searchParamExtractor() { - return new SearchParamExtractorDstu3(); - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java deleted file mode 100644 index cf7e19f4973..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java +++ /dev/null @@ -1,47 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.config; - -/*- - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -public class SearchParamR4Config extends BaseSearchParamConfig { - @Bean - @Primary - public FhirContext fhirContextR4() { - FhirContext retVal = FhirContext.forR4(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorR4 searchParamExtractor() { - return new SearchParamExtractorR4(); - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index eb44a8a0a7a..355ebf37bec 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -20,18 +20,40 @@ package ca.uhn.fhir.jpa.searchparam.extractor; * #L% */ -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.ObjectUtils; import org.hibernate.search.spatial.impl.Point; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseEnumeration; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -40,11 +62,21 @@ import javax.measure.quantity.Quantity; import javax.measure.unit.NonSI; import javax.measure.unit.Unit; import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { private static final Pattern SPLIT = Pattern.compile("\\||( or )"); @@ -59,6 +91,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor private ISearchParamRegistry mySearchParamRegistry; @Autowired private ModelConfig myModelConfig; + @Autowired + private PartitionSettings myPartitionSettings; private Set myIgnoredForSearchDatatypes; private BaseRuntimeChildDefinition myQuantityValueValueChild; private BaseRuntimeChildDefinition myQuantitySystemValueChild; @@ -97,7 +131,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor private BaseRuntimeChildDefinition myCodingDisplayValueChild; private BaseRuntimeChildDefinition myContactPointSystemValueChild; private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild; - /** * Constructor */ @@ -113,6 +146,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor mySearchParamRegistry = theSearchParamRegistry; } + @VisibleForTesting + public BaseSearchParamExtractor setPartitionConfigForUnitTest(PartitionSettings thePartitionSettings) { + myPartitionSettings = thePartitionSettings; + return this; + } + @Override public SearchParamSet extractResourceLinks(IBaseResource theResource) { IExtractor extractor = (params, searchParam, value, path) -> { @@ -125,23 +164,29 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor case "uri": case "canonical": String typeName = toTypeName(value); + IPrimitiveType valuePrimitive = (IPrimitiveType) value; + IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance(); + fakeReference.setReference(valuePrimitive.getValueAsString()); // Canonical has a root type of "uri" if ("canonical".equals(typeName)) { - IPrimitiveType valuePrimitive = (IPrimitiveType) value; - IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance(); - fakeReference.setReference(valuePrimitive.getValueAsString()); /* * See #1583 * Technically canonical fields should not allow local references (e.g. * Questionnaire/123) but it seems reasonable for us to interpret a canonical - * containing a local reference for what it is, and allow people to seaerch + * containing a local reference for what it is, and allow people to search * based on that. */ IIdType parsed = fakeReference.getReferenceElement(); if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) { - PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference); + PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference, false); + params.add(ref); + break; + } + + if (parsed.isAbsolute()) { + PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference, true); params.add(ref); break; } @@ -165,7 +210,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return; } - PathAndRef ref = new PathAndRef(searchParam.getName(), path, valueRef); + PathAndRef ref = new PathAndRef(searchParam.getName(), path, valueRef, false); params.add(ref); break; default: @@ -180,7 +225,19 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Override public SearchParamSet extractSearchParamTokens(IBaseResource theResource) { + IExtractor extractor = createTokenExtractor(theResource); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN); + } + @Override + public SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) { + IExtractor extractor = createTokenExtractor(theResource); + SearchParamSet setToPopulate = new SearchParamSet<>(); + extractSearchParam(theSearchParam, theResource, extractor, setToPopulate); + return setToPopulate; + } + + private IExtractor createTokenExtractor(IBaseResource theResource) { String resourceTypeName = toRootTypeName(theResource); String useSystem; if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { @@ -198,85 +255,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } } - IExtractor extractor = (params, searchParam, value, path) -> { - - // DSTU3+ - if (value instanceof IBaseEnumeration) { - IBaseEnumeration obj = (IBaseEnumeration) value; - String system = extractSystem(obj); - String code = obj.getValueAsString(); - createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code); - return; - } - - // DSTU2 only - if (value instanceof BoundCodeDt) { - BoundCodeDt boundCode = (BoundCodeDt) value; - Enum valueAsEnum = boundCode.getValueAsEnum(); - String system = null; - if (valueAsEnum != null) { - //noinspection unchecked - system = boundCode.getBinder().toSystemString(valueAsEnum); - } - String code = boundCode.getValueAsString(); - createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code); - return; - } - - if (value instanceof IPrimitiveType) { - IPrimitiveType nextValue = (IPrimitiveType) value; - String systemAsString = null; - String valueAsString = nextValue.getValueAsString(); - if ("CodeSystem.concept.code".equals(path)) { - systemAsString = useSystem; - } else if ("ValueSet.codeSystem.concept.code".equals(path)) { - systemAsString = useSystem; - } - - createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, systemAsString, valueAsString); - return; - } - - switch (path) { - case "Patient.communication": - addToken_PatientCommunication(resourceTypeName, params, searchParam, value); - return; - case "Consent.source": - // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that - return; - case "Location.position": - addCoords_Position(resourceTypeName, params, searchParam, value); - return; - case "StructureDefinition.context": - // TODO: implement this - ourLog.warn("StructureDefinition context indexing not currently supported"); - return; - case "CapabilityStatement.rest.security": - addToken_CapabilityStatementRestSecurity(resourceTypeName, params, searchParam, value); - return; - } - - String nextType = toRootTypeName(value); - switch (nextType) { - case "Identifier": - addToken_Identifier(resourceTypeName, params, searchParam, value); - break; - case "CodeableConcept": - addToken_CodeableConcept(resourceTypeName, params, searchParam, value); - break; - case "Coding": - addToken_Coding(resourceTypeName, params, searchParam, value); - break; - case "ContactPoint": - addToken_ContactPoint(resourceTypeName, params, searchParam, value); - break; - default: - addUnexpectedDatatypeWarning(params, searchParam, value); - break; - } - }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN); + return new TokenExtractor(resourceTypeName, useSystem); } @Override @@ -515,7 +494,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String system = extractValueAsString(myQuantitySystemValueChild, theValue); String code = extractValueAsString(myQuantityCodeValueChild, theValue); - ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theResourceType, theSearchParam.getName(), nextValueValue, system, code); + ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code); theParams.add(nextEntity); } @@ -530,7 +509,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String nextValueString = "urn:iso:std:iso:4217"; String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue); String searchParamName = theSearchParam.getName(); - ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode); + ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode); theParams.add(nextEntity); } @@ -608,7 +587,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String endAsString = extractValueAsString(myPeriodEndValueChild, theValue); if (start != null || end != null) { - ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString); + ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings ,theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString); theParams.add(nextEntity); } } @@ -623,7 +602,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor for (IPrimitiveType nextEvent : values) { if (nextEvent.getValue() != null) { dates.add(nextEvent.getValue()); - if (firstValue == null) { firstValue = nextEvent.getValueAsString(); } @@ -652,7 +630,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } if (!dates.isEmpty()) { - ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue); + ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue); theParams.add(nextEntity); } } @@ -663,7 +641,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue); if (value != null) { value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), value); + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value); theParams.add(nextEntity); } } @@ -674,7 +652,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String system = extractValueAsString(myQuantitySystemValueChild, theValue); String code = extractValueAsString(myQuantityCodeValueChild, theValue); value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), value); + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value); theParams.add(nextEntity); } } @@ -684,7 +662,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor IPrimitiveType value = (IPrimitiveType) theValue; if (value.getValue() != null) { BigDecimal valueDecimal = new BigDecimal(value.getValue()); - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), valueDecimal); + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); theParams.add(nextEntity); } @@ -695,7 +673,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor IPrimitiveType value = (IPrimitiveType) theValue; if (value.getValue() != null) { BigDecimal valueDecimal = value.getValue(); - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(theResourceType, theSearchParam.getName(), valueDecimal); + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); theParams.add(nextEntity); } @@ -722,7 +700,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor if (latitude != null && longitude != null) { double normalizedLatitude = Point.normalizeLatitude(latitude.doubleValue()); double normalizedLongitude = Point.normalizeLongitude(longitude.doubleValue()); - ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude); + ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(myPartitionSettings, theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude); theParams.add(nextEntity); } } @@ -803,25 +781,29 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor continue; } - String nextPathUnsplit = nextSpDef.getPath(); - if (isBlank(nextPathUnsplit)) { - continue; - } + extractSearchParam(nextSpDef, theResource, theExtractor, retVal); + } + return retVal; + } - String[] splitPaths = split(nextPathUnsplit); - for (String nextPath : splitPaths) { - nextPath = trim(nextPath); - for (IBase nextObject : extractValues(nextPath, theResource)) { - if (nextObject != null) { - String typeName = toRootTypeName(nextObject); - if (!myIgnoredForSearchDatatypes.contains(typeName)) { - theExtractor.extract(retVal, nextSpDef, nextObject, nextPath); - } + private void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor theExtractor, SearchParamSet theSetToPopulate) { + String nextPathUnsplit = theSearchParameterDef.getPath(); + if (isBlank(nextPathUnsplit)) { + return; + } + + String[] splitPaths = split(nextPathUnsplit); + for (String nextPath : splitPaths) { + nextPath = trim(nextPath); + for (IBase nextObject : extractValues(nextPath, theResource)) { + if (nextObject != null) { + String typeName = toRootTypeName(nextObject); + if (!myIgnoredForSearchDatatypes.contains(typeName)) { + theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath); } } } } - return retVal; } private String toRootTypeName(IBase nextObject) { @@ -839,7 +821,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor private void addDateTimeTypes(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { IPrimitiveType nextBaseDateTime = (IPrimitiveType) theValue; if (nextBaseDateTime.getValue() != null) { - ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString()); + ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(myPartitionSettings ,theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString()); theParams.add(param); } } @@ -849,7 +831,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor IPrimitiveType value = (IPrimitiveType) theValue; String valueAsString = value.getValueAsString(); if (isNotBlank(valueAsString)) { - ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(theResourceType, theSearchParam.getName(), valueAsString); + ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(myPartitionSettings, theResourceType, theSearchParam.getName(), valueAsString); theParams.add(nextEntity); } } @@ -868,7 +850,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor valueNormalized = valueNormalized.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); } - ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(getModelConfig(), theResourceType, searchParamName, valueNormalized, value); + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionSettings, getModelConfig(), theResourceType, searchParamName, valueNormalized, value); Set params = theParams; params.add(nextEntity); @@ -887,7 +869,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } ResourceIndexedSearchParamToken nextEntity; - nextEntity = new ResourceIndexedSearchParamToken(theResourceType, theSearchParam.getName(), system, value); + nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, theSearchParam.getName(), system, value); theParams.add(nextEntity); } } @@ -1037,6 +1019,99 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } + private class TokenExtractor implements IExtractor { + private final String myResourceTypeName; + private final String myUseSystem; + + public TokenExtractor(String theResourceTypeName, String theUseSystem) { + myResourceTypeName = theResourceTypeName; + myUseSystem = theUseSystem; + } + + @Override + public void extract(SearchParamSet params, RuntimeSearchParam searchParam, IBase value, String path) { + + // DSTU3+ + if (value instanceof IBaseEnumeration) { + IBaseEnumeration obj = (IBaseEnumeration) value; + String system = extractSystem(obj); + String code = obj.getValueAsString(); + BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code); + return; + } + + // DSTU2 only + if (value instanceof BoundCodeDt) { + BoundCodeDt boundCode = (BoundCodeDt) value; + Enum valueAsEnum = boundCode.getValueAsEnum(); + String system = null; + if (valueAsEnum != null) { + //noinspection unchecked + system = boundCode.getBinder().toSystemString(valueAsEnum); + } + String code = boundCode.getValueAsString(); + BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code); + return; + } + + if (value instanceof IPrimitiveType) { + IPrimitiveType nextValue = (IPrimitiveType) value; + String systemAsString = null; + String valueAsString = nextValue.getValueAsString(); + if ("CodeSystem.concept.code".equals(path)) { + systemAsString = myUseSystem; + } else if ("ValueSet.codeSystem.concept.code".equals(path)) { + systemAsString = myUseSystem; + } + + if (value instanceof IIdType) { + valueAsString = ((IIdType) value).getIdPart(); + } + + BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, systemAsString, valueAsString); + return; + } + + switch (path) { + case "Patient.communication": + BaseSearchParamExtractor.this.addToken_PatientCommunication(myResourceTypeName, params, searchParam, value); + return; + case "Consent.source": + // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that + return; + case "Location.position": + BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value); + return; + case "StructureDefinition.context": + // TODO: implement this + ourLog.warn("StructureDefinition context indexing not currently supported"); + return; + case "CapabilityStatement.rest.security": + BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(myResourceTypeName, params, searchParam, value); + return; + } + + String nextType = BaseSearchParamExtractor.this.toRootTypeName(value); + switch (nextType) { + case "Identifier": + BaseSearchParamExtractor.this.addToken_Identifier(myResourceTypeName, params, searchParam, value); + break; + case "CodeableConcept": + BaseSearchParamExtractor.this.addToken_CodeableConcept(myResourceTypeName, params, searchParam, value); + break; + case "Coding": + BaseSearchParamExtractor.this.addToken_Coding(myResourceTypeName, params, searchParam, value); + break; + case "ContactPoint": + BaseSearchParamExtractor.this.addToken_ContactPoint(myResourceTypeName, params, searchParam, value); + break; + default: + BaseSearchParamExtractor.this.addUnexpectedDatatypeWarning(params, searchParam, value); + break; + } + } + } + private static void addIgnoredType(FhirContext theCtx, String theType, Set theIgnoredTypes) { BaseRuntimeElementDefinition elementDefinition = theCtx.getElementDefinition(theType); if (elementDefinition != null) { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java index fa04e0784b8..f0da03abc27 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java @@ -21,14 +21,33 @@ package ca.uhn.fhir.jpa.searchparam.extractor; */ import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; public interface IResourceLinkResolver { - ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest); + + /** + * This method resolves the target of a reference found within a resource that is being created/updated. We do this + * so that we can create indexed links between resources, and so that we can validate that the target actually + * exists in cases where we need to check that. + *

        + * This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource. + * + * @param theRequestPartitionId The partition ID of the target resource + * @param theSearchParam The param that is being indexed + * @param theSourcePath The path within the resource where this reference was found + * @param theSourceResourceId The ID of the resource containing the reference to the target being resolved + * @param theTypeString The type of the resource being resolved + * @param theType The resource type of the target + * @param theReference The reference being resolved + * @param theRequest The incoming request, if any + */ + IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest); void validateTypeOrThrowException(Class theType); + } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java index 1f9ef8bb3ea..15e4479ab8c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.model.entity.*; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -42,6 +43,8 @@ public interface ISearchParamExtractor { SearchParamSet extractSearchParamTokens(IBaseResource theResource); + SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam); + SearchParamSet extractSearchParamSpecial(IBaseResource theResource); SearchParamSet extractSearchParamUri(IBaseResource theResource); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java index 82cb389d15f..22bc6d65b3d 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java @@ -27,15 +27,21 @@ public class PathAndRef { private final String myPath; private final IBaseReference myRef; private final String mySearchParamName; + private final boolean myCanonical; /** * Constructor */ - public PathAndRef(String theSearchParamName, String thePath, IBaseReference theRef) { + public PathAndRef(String theSearchParamName, String thePath, IBaseReference theRef, boolean theCanonical) { super(); mySearchParamName = theSearchParamName; myPath = thePath; myRef = theRef; + myCanonical = theCanonical; + } + + public boolean isCanonical() { + return myCanonical; } public String getSearchParamName() { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java index 9dff7395b62..05df1aaef70 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java @@ -21,22 +21,28 @@ package ca.uhn.fhir.jpa.searchparam.extractor; */ import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.ReferenceParam; import org.apache.commons.lang3.StringUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.function.Predicate; import static org.apache.commons.lang3.StringUtils.compare; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public final class ResourceIndexedSearchParams { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class); - final public Collection myStringParams = new ArrayList<>(); final public Collection myTokenParams = new HashSet<>(); final public Collection myNumberParams = new ArrayList<>(); @@ -84,7 +90,6 @@ public final class ResourceIndexedSearchParams { } - public Collection getResourceLinks() { return myLinks; } @@ -109,7 +114,7 @@ public final class ResourceIndexedSearchParams { theEntity.setHasLinks(myLinks.isEmpty() == false); } - public void setUpdatedTime(Date theUpdateTime) { + void setUpdatedTime(Date theUpdateTime) { setUpdatedTime(myStringParams, theUpdateTime); setUpdatedTime(myNumberParams, theUpdateTime); setUpdatedTime(myQuantityParams, theUpdateTime); @@ -125,6 +130,186 @@ public final class ResourceIndexedSearchParams { } } + public void calculateHashes(Collection theStringParams) { + for (BaseResourceIndex next : theStringParams) { + next.calculateHashes(); + } + } + + public Set getPopulatedResourceLinkParameters() { + return myPopulatedResourceLinkParameters; + } + + public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) { + if (theParamDef == null) { + return false; + } + Collection resourceParams; + switch (theParamDef.getParamType()) { + case TOKEN: + resourceParams = myTokenParams; + break; + case QUANTITY: + resourceParams = myQuantityParams; + break; + case STRING: + resourceParams = myStringParams; + break; + case NUMBER: + resourceParams = myNumberParams; + break; + case URI: + resourceParams = myUriParams; + break; + case DATE: + resourceParams = myDateParams; + break; + case REFERENCE: + return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath()); + case COMPOSITE: + case HAS: + case SPECIAL: + default: + resourceParams = null; + } + if (resourceParams == null) { + return false; + } + Predicate namedParamPredicate = param -> + param.getParamName().equalsIgnoreCase(theParamName) && + param.matches(theParam, theUseOrdinalDatesForDayComparison); + + return resourceParams.stream().anyMatch(namedParamPredicate); + } + + /** + * @deprecated Replace with the method below + */ + // KHS This needs to be public as libraries outside of hapi call it directly + @Deprecated + public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) { + return matchResourceLinks(new ModelConfig(), theResourceName, theParamName, theParam, theParamPath); + } + + // KHS This needs to be public as libraries outside of hapi call it directly + public boolean matchResourceLinks(ModelConfig theModelConfig, String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) { + ReferenceParam reference = (ReferenceParam) theParam; + + Predicate namedParamPredicate = resourceLink -> + searchParameterPathMatches(theResourceName, resourceLink, theParamName, theParamPath) + && resourceIdMatches(theModelConfig, resourceLink, reference); + + return myLinks.stream().anyMatch(namedParamPredicate); + } + + private boolean resourceIdMatches(ModelConfig theModelConfig, ResourceLink theResourceLink, ReferenceParam theReference) { + String baseUrl = theReference.getBaseUrl(); + if (isNotBlank(baseUrl)) { + if (!theModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl)) { + return false; + } + } + + String targetType = theResourceLink.getTargetResourceType(); + String targetId = theResourceLink.getTargetResourceId(); + + assert isNotBlank(targetType); + assert isNotBlank(targetId); + + if (theReference.hasResourceType()) { + if (!theReference.getResourceType().equals(targetType)) { + return false; + } + } + + if (!targetId.equals(theReference.getIdPart())) { + return false; + } + + return true; + } + + private boolean searchParameterPathMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) { + String sourcePath = theResourceLink.getSourcePath(); + return sourcePath.equalsIgnoreCase(theParamPath); + } + + @Override + public String toString() { + return "ResourceIndexedSearchParams{" + + "stringParams=" + myStringParams + + ", tokenParams=" + myTokenParams + + ", numberParams=" + myNumberParams + + ", quantityParams=" + myQuantityParams + + ", dateParams=" + myDateParams + + ", uriParams=" + myUriParams + + ", coordsParams=" + myCoordsParams + + ", compositeStringUniques=" + myCompositeStringUniques + + ", links=" + myLinks + + '}'; + } + + public void findMissingSearchParams(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, ResourceTable theEntity, Set> theActiveSearchParams) { + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams); + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams); + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams); + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams); + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams); + findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams); + } + + @SuppressWarnings("unchecked") + private void findMissingSearchParams(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, ResourceTable theEntity, Set> activeSearchParams, RestSearchParameterTypeEnum type, + Collection paramCollection) { + for (Map.Entry nextEntry : activeSearchParams) { + String nextParamName = nextEntry.getKey(); + if (nextEntry.getValue().getParamType() == type) { + boolean haveParam = false; + for (BaseResourceIndexedSearchParam nextParam : paramCollection) { + if (nextParam.getParamName().equals(nextParamName)) { + haveParam = true; + break; + } + } + + if (!haveParam) { + BaseResourceIndexedSearchParam param; + switch (type) { + case DATE: + param = new ResourceIndexedSearchParamDate(); + break; + case NUMBER: + param = new ResourceIndexedSearchParamNumber(); + break; + case QUANTITY: + param = new ResourceIndexedSearchParamQuantity(); + break; + case STRING: + param = new ResourceIndexedSearchParamString() + .setModelConfig(theModelConfig); + break; + case TOKEN: + param = new ResourceIndexedSearchParamToken(); + break; + case URI: + param = new ResourceIndexedSearchParamUri(); + break; + case COMPOSITE: + case HAS: + case REFERENCE: + case SPECIAL: + default: + continue; + } + param.setPartitionSettings(thePartitionSettings); + param.setResource(theEntity); + param.setMissing(true); + param.setParamName(nextParamName); + paramCollection.add((RT) param); + } + } + } + } /** * This method is used to create a set of all possible combinations of @@ -206,169 +391,4 @@ public final class ResourceIndexedSearchParams { } - - public void calculateHashes(Collection theStringParams) { - for (BaseResourceIndex next : theStringParams) { - next.calculateHashes(); - } - } - - public Set getPopulatedResourceLinkParameters() { - return myPopulatedResourceLinkParameters; - } - - public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) { - if (theParamDef == null) { - return false; - } - Collection resourceParams; - switch (theParamDef.getParamType()) { - case TOKEN: - resourceParams = myTokenParams; - break; - case QUANTITY: - resourceParams = myQuantityParams; - break; - case STRING: - resourceParams = myStringParams; - break; - case NUMBER: - resourceParams = myNumberParams; - break; - case URI: - resourceParams = myUriParams; - break; - case DATE: - resourceParams = myDateParams; - break; - case REFERENCE: - return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath()); - case COMPOSITE: - case HAS: - case SPECIAL: - default: - resourceParams = null; - } - if (resourceParams == null) { - return false; - } - Predicate namedParamPredicate = param -> - param.getParamName().equalsIgnoreCase(theParamName) && - param.matches(theParam, theUseOrdinalDatesForDayComparison); - - return resourceParams.stream().anyMatch(namedParamPredicate); - } - - // KHS This needs to be public as libraries outside of hapi call it directly - public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) { - ReferenceParam reference = (ReferenceParam)theParam; - - Predicate namedParamPredicate = resourceLink -> - resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath) - && resourceIdMatches(resourceLink, reference); - - return myLinks.stream().anyMatch(namedParamPredicate); - } - - private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) { - ResourceTable target = theResourceLink.getTargetResource(); - IdDt idDt = target.getIdDt(); - if (idDt.isIdPartValidLong()) { - if (theReference.isIdPartValidLong()) { - return theReference.getIdPartAsLong().equals(idDt.getIdPartAsLong()); - } else { - return false; - } - } else { - ForcedId forcedId = target.getForcedId(); - if (forcedId != null) { - return forcedId.getForcedId().equals(theReference.getValue()); - } else { - return false; - } - } - } - - private boolean resourceLinkMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) { - return theResourceLink.getTargetResource().getResourceType().equalsIgnoreCase(theParamName) || - theResourceLink.getSourcePath().equalsIgnoreCase(theParamPath); - } - - @Override - public String toString() { - return "ResourceIndexedSearchParams{" + - "stringParams=" + myStringParams + - ", tokenParams=" + myTokenParams + - ", numberParams=" + myNumberParams + - ", quantityParams=" + myQuantityParams + - ", dateParams=" + myDateParams + - ", uriParams=" + myUriParams + - ", coordsParams=" + myCoordsParams + - ", compositeStringUniques=" + myCompositeStringUniques + - ", links=" + myLinks + - '}'; - } - - public void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set> theActiveSearchParams) { - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams); - } - - @SuppressWarnings("unchecked") - private void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set> activeSearchParams, RestSearchParameterTypeEnum type, - Collection paramCollection) { - for (Map.Entry nextEntry : activeSearchParams) { - String nextParamName = nextEntry.getKey(); - if (nextEntry.getValue().getParamType() == type) { - boolean haveParam = false; - for (BaseResourceIndexedSearchParam nextParam : paramCollection) { - if (nextParam.getParamName().equals(nextParamName)) { - haveParam = true; - break; - } - } - - if (!haveParam) { - BaseResourceIndexedSearchParam param; - switch (type) { - case DATE: - param = new ResourceIndexedSearchParamDate(); - break; - case NUMBER: - param = new ResourceIndexedSearchParamNumber(); - break; - case QUANTITY: - param = new ResourceIndexedSearchParamQuantity(); - break; - case STRING: - param = new ResourceIndexedSearchParamString() - .setModelConfig(theModelConfig); - break; - case TOKEN: - param = new ResourceIndexedSearchParamToken(); - break; - case URI: - param = new ResourceIndexedSearchParamUri(); - break; - case COMPOSITE: - case HAS: - case REFERENCE: - case SPECIAL: - default: - continue; - } - param.setResource(theEntity); - param.setMissing(true); - param.setParamName(nextParamName); - paramCollection.add((RT) param); - } - } - } - } - - } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java deleted file mode 100644 index dbd8f79f9d9..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java +++ /dev/null @@ -1,171 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.extractor; - -/*- - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.model.entity.ResourceLink; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Date; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -@Service -public class ResourceLinkExtractor { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceLinkExtractor.class); - - @Autowired - private ModelConfig myModelConfig; - @Autowired - private FhirContext myContext; - @Autowired - private ISearchParamRegistry mySearchParamRegistry; - @Autowired - private ISearchParamExtractor mySearchParamExtractor; - @Autowired - private IInterceptorBroadcaster myInterceptorBroadcaster; - - public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, boolean theFailOnInvalidReference, RequestDetails theRequest) { - String resourceName = myContext.getResourceDefinition(theResource).getName(); - - ISearchParamExtractor.SearchParamSet refs = mySearchParamExtractor.extractResourceLinks(theResource); - SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs); - for (PathAndRef nextPathAndRef : refs) { - RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName()); - extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest); - } - - theEntity.setHasLinks(theParams.myLinks.size() > 0); - } - - private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest) { - IBaseReference nextReference = thePathAndRef.getRef(); - IIdType nextId = nextReference.getReferenceElement(); - String path = thePathAndRef.getPath(); - - /* - * This can only really happen if the DAO is being called - * programmatically with a Bundle (not through the FHIR REST API) - * but Smile does this - */ - if (nextId.isEmpty() && nextReference.getResource() != null) { - nextId = nextReference.getResource().getIdElement(); - } - - theParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName()); - - if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId)) { - ResourceLink resourceLink = new ResourceLink(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime); - if (theParams.myLinks.add(resourceLink)) { - ourLog.debug("Indexing remote resource reference URL: {}", nextId); - } - return; - } - - String baseUrl = nextId.getBaseUrl(); - String typeString = nextId.getResourceType(); - if (isBlank(typeString)) { - String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " + nextId.getValue(); - if (theFailOnInvalidReference) { - throw new InvalidRequestException(msg); - } else { - ourLog.debug(msg); - return; - } - } - RuntimeResourceDefinition resourceDefinition; - try { - resourceDefinition = myContext.getResourceDefinition(typeString); - } catch (DataFormatException e) { - String msg = "Invalid resource reference found at path[" + path + "] - Resource type is unknown or not supported on this server - " + nextId.getValue(); - if (theFailOnInvalidReference) { - throw new InvalidRequestException(msg); - } else { - ourLog.debug(msg); - return; - } - } - - if (theRuntimeSearchParam.hasTargets()) { - if (!theRuntimeSearchParam.getTargets().contains(typeString)) { - return; - } - } - - if (isNotBlank(baseUrl)) { - if (!myModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myModelConfig.isAllowExternalReferences()) { - String msg = myContext.getLocalizer().getMessage(BaseSearchParamExtractor.class, "externalReferenceNotAllowed", nextId.getValue()); - throw new InvalidRequestException(msg); - } else { - ResourceLink resourceLink = new ResourceLink(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime); - if (theParams.myLinks.add(resourceLink)) { - ourLog.debug("Indexing remote resource reference URL: {}", nextId); - } - return; - } - } - - Class type = resourceDefinition.getImplementingClass(); - String id = nextId.getIdPart(); - if (StringUtils.isBlank(id)) { - String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue(); - if (theFailOnInvalidReference) { - throw new InvalidRequestException(msg); - } else { - ourLog.debug(msg); - return; - } - } - - theResourceLinkResolver.validateTypeOrThrowException(type); - ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest); - if (resourceLink == null) { - return; - } - theParams.myLinks.add(resourceLink); - } - - private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest) { - ResourceTable targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest); - - if (targetResource == null) { - return null; - } - - return new ResourceLink(nextPathAndRef.getPath(), theEntity, targetResource, theUpdateTime); - } - -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java index eb9e165174f..79a7de05c92 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java @@ -21,12 +21,12 @@ package ca.uhn.fhir.jpa.searchparam.extractor; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.dstu3.context.IWorkerContext; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.Base; import org.hl7.fhir.dstu3.utils.FHIRPathEngine; import org.hl7.fhir.instance.model.api.IBase; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 1c15804d01f..f7d216ee66a 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import com.google.common.annotations.VisibleForTesting; @@ -30,12 +31,20 @@ import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ResourceType; +import org.hl7.fhir.r4.model.TypeDetails; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.utils.FHIRPathEngine; import javax.annotation.PostConstruct; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java index d27f51fc814..b882db0f4a5 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java @@ -21,14 +21,14 @@ package ca.uhn.fhir.jpa.searchparam.extractor; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.Resource; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 95ce06c08de..b656e8f63e4 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -20,24 +20,39 @@ package ca.uhn.fhir.jpa.searchparam.extractor; * #L% */ +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; -@Service -@Lazy public class SearchParamExtractorService { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorService.class); @@ -45,30 +60,62 @@ public class SearchParamExtractorService { private ISearchParamExtractor mySearchParamExtractor; @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private ModelConfig myModelConfig; + @Autowired + private FhirContext myContext; + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + @Autowired + private PartitionSettings myPartitionSettings; + @Autowired(required = false) + private IResourceLinkResolver myResourceLinkResolver; - public void extractFromResource(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) { + /** + * This method is responsible for scanning a resource for all of the search parameter instances. I.e. for all search parameters defined for + * a given resource type, it extracts the associated indexes and populates {@literal theParams}. + */ + public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean theFailOnInvalidReference) { + IBaseResource resource = normalizeResource(theResource); + + // All search parameter types except Reference + extractSearchIndexParameters(theRequestDetails, theParams, resource, theEntity); + + // Reference search parameters + extractResourceLinks(theRequestPartitionId, theParams, theEntity, resource, theUpdateTime, theFailOnInvalidReference, theRequestDetails); + + theParams.setUpdatedTime(theUpdateTime); + } + + private void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) { + + // Strings ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource); handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings); theParams.myStringParams.addAll(strings); + // Numbers ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource); handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers); theParams.myNumberParams.addAll(numbers); + // Quantities ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource); handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities); theParams.myQuantityParams.addAll(quantities); + // Dates ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource); handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates); theParams.myDateParams.addAll(dates); + // URIs ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource); handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris); theParams.myUriParams.addAll(uris); - ourLog.trace("Storing date indexes: {}", theParams.myDateParams); - + // Tokens (can result in both Token and String, as we index the display name for + // the types: Coding, CodeableConcept) for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theResource)) { if (next instanceof ResourceIndexedSearchParamToken) { theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next); @@ -79,21 +126,188 @@ public class SearchParamExtractorService { } } + // Specials for (BaseResourceIndexedSearchParam next : extractSearchParamSpecial(theResource)) { if (next instanceof ResourceIndexedSearchParamCoords) { theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next); } } - populateResourceTable(theParams.myStringParams, theEntity); + // Do this after, because we add to strings during both string and token processing populateResourceTable(theParams.myNumberParams, theEntity); populateResourceTable(theParams.myQuantityParams, theEntity); populateResourceTable(theParams.myDateParams, theEntity); populateResourceTable(theParams.myUriParams, theEntity); - populateResourceTable(theParams.myCoordsParams, theEntity); populateResourceTable(theParams.myTokenParams, theEntity); + populateResourceTable(theParams.myStringParams, theEntity); + populateResourceTable(theParams.myCoordsParams, theEntity); + } + /** + * This is a bit hacky, but if someone has manually populated a resource (ie. my working directly with the model + * as opposed to by parsing a serialized instance) it's possible that they have put in contained resources + * using {@link IBaseReference#setResource(IBaseResource)}, and those contained resources have not yet + * ended up in the Resource.contained array, meaning that FHIRPath expressions won't be able to find them. + * + * As a result, we to a serialize-and-parse to normalize the object. This really only affects people who + * are calling the JPA DAOs directly, but there are a few of those... + */ + private IBaseResource normalizeResource(IBaseResource theResource) { + IParser parser = myContext.newJsonParser().setPrettyPrint(false); + theResource = parser.parseResource(parser.encodeResourceToString(theResource)); + return theResource; + } + + private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean theFailOnInvalidReference, RequestDetails theRequest) { + String resourceName = myContext.getResourceDefinition(theResource).getName(); + + ISearchParamExtractor.SearchParamSet refs = mySearchParamExtractor.extractResourceLinks(theResource); + SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs); + + Map resourceIdToResolvedTarget = new HashMap<>(); + for (PathAndRef nextPathAndRef : refs) { + RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName()); + extractResourceLinks(theRequestPartitionId, theParams, theEntity, theUpdateTime, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest, resourceIdToResolvedTarget); + } + + theEntity.setHasLinks(theParams.myLinks.size() > 0); + } + + private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map theResourceIdToResolvedTarget) { + IBaseReference nextReference = thePathAndRef.getRef(); + IIdType nextId = nextReference.getReferenceElement(); + String path = thePathAndRef.getPath(); + + /* + * This can only really happen if the DAO is being called + * programmatically with a Bundle (not through the FHIR REST API) + * but Smile does this + */ + if (nextId.isEmpty() && nextReference.getResource() != null) { + nextId = nextReference.getResource().getIdElement(); + } + + theParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName()); + + boolean canonical = thePathAndRef.isCanonical(); + if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId) || canonical) { + String value = nextId.getValue(); + ResourceLink resourceLink = ResourceLink.forLogicalReference(thePathAndRef.getPath(), theEntity, value, theUpdateTime); + if (theParams.myLinks.add(resourceLink)) { + ourLog.debug("Indexing remote resource reference URL: {}", nextId); + } + return; + } + + String baseUrl = nextId.getBaseUrl(); + String typeString = nextId.getResourceType(); + if (isBlank(typeString)) { + String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " + nextId.getValue(); + if (theFailOnInvalidReference) { + throw new InvalidRequestException(msg); + } else { + ourLog.debug(msg); + return; + } + } + RuntimeResourceDefinition resourceDefinition; + try { + resourceDefinition = myContext.getResourceDefinition(typeString); + } catch (DataFormatException e) { + String msg = "Invalid resource reference found at path[" + path + "] - Resource type is unknown or not supported on this server - " + nextId.getValue(); + if (theFailOnInvalidReference) { + throw new InvalidRequestException(msg); + } else { + ourLog.debug(msg); + return; + } + } + + if (theRuntimeSearchParam.hasTargets()) { + if (!theRuntimeSearchParam.getTargets().contains(typeString)) { + return; + } + } + + if (isNotBlank(baseUrl)) { + if (!myModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myModelConfig.isAllowExternalReferences()) { + String msg = myContext.getLocalizer().getMessage(BaseSearchParamExtractor.class, "externalReferenceNotAllowed", nextId.getValue()); + throw new InvalidRequestException(msg); + } else { + ResourceLink resourceLink = ResourceLink.forAbsoluteReference(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime); + if (theParams.myLinks.add(resourceLink)) { + ourLog.debug("Indexing remote resource reference URL: {}", nextId); + } + return; + } + } + + Class type = resourceDefinition.getImplementingClass(); + String id = nextId.getIdPart(); + if (StringUtils.isBlank(id)) { + String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue(); + if (theFailOnInvalidReference) { + throw new InvalidRequestException(msg); + } else { + ourLog.debug(msg); + return; + } + } + + ResourceLink resourceLink; + if (theFailOnInvalidReference) { + + myResourceLinkResolver.validateTypeOrThrowException(type); + resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theRequestPartitionId, theEntity, theUpdateTime, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theResourceIdToResolvedTarget); + if (resourceLink == null) { + return; + } + + } else { + + ResourceTable target; + target = new ResourceTable(); + target.setResourceType(typeString); + resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, nextId.getIdPart(), theUpdateTime); + + } + + theParams.myLinks.add(resourceLink); + } + + private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest, Map theResourceIdToResolvedTarget) { + /* + * We keep a cache of resolved target resources. This is good since for some resource types, there + * are multiple search parameters that map to the same element path within a resource (e.g. + * Observation:patient and Observation.subject and we don't want to force a resolution of the + * target any more times than we have to. + */ + + RequestPartitionId targetRequestPartitionId = theRequestPartitionId; + if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) { + targetRequestPartitionId = null; + } + + String key = RequestPartitionId.stringifyForKey(targetRequestPartitionId) + "/" + theNextId.getValue(); + IResourceLookup targetResource = theResourceIdToResolvedTarget.get(key); + if (targetResource == null) { + targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest); + } + + if (targetResource == null) { + return null; + } + + theResourceIdToResolvedTarget.put(key, targetResource); + + String targetResourceType = targetResource.getResourceType(); + Long targetResourcePid = targetResource.getResourceId(); + String targetResourceIdPart = theNextId.getIdPart(); + return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime); + } + + static void handleWarnings(RequestDetails theRequestDetails, IInterceptorBroadcaster theInterceptorBroadcaster, ISearchParamExtractor.SearchParamSet theSearchParamSet) { if (theSearchParamSet.getWarnings().isEmpty()) { return; @@ -113,7 +327,9 @@ public class SearchParamExtractorService { private void populateResourceTable(Collection theParams, ResourceTable theResourceTable) { for (BaseResourceIndexedSearchParam next : theParams) { - next.setResource(theResourceTable); + if (next.getResourcePid() == null) { + next.setResource(theResourceTable); + } } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java index 2b52d347e91..08492d2317b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java @@ -30,7 +30,6 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.util.SourceParam; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.BaseParamWithPrefix; @@ -45,13 +44,11 @@ import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Optional; -@Service public class InMemoryResourceMatcher { @Autowired @@ -146,7 +143,7 @@ public class InMemoryResourceMatcher { case Constants.PARAM_SOURCE: return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource)); default: - return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef); + return matchResourceParam(myModelConfig, theParamName, theAndOrParams, theSearchParams, resourceName, paramDef); } } @@ -189,7 +186,7 @@ public class InMemoryResourceMatcher { return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart()); } - private InMemoryMatchResult matchResourceParam(String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { + private InMemoryMatchResult matchResourceParam(ModelConfig theModelConfig, String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { if (theParamDef != null) { switch (theParamDef.getParamType()) { case QUANTITY: @@ -202,7 +199,7 @@ public class InMemoryResourceMatcher { if (theSearchParams == null) { return InMemoryMatchResult.successfulMatch(); } else { - return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams, myModelConfig.getUseOrdinalDatesForDayPrecisionSearches()))); + return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theModelConfig, theResourceName, theParamName, theParamDef, nextAnd, theSearchParams, myModelConfig.getUseOrdinalDatesForDayPrecisionSearches()))); } case COMPOSITE: case HAS: @@ -219,28 +216,8 @@ public class InMemoryResourceMatcher { } } - private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List theNextAnd, ResourceIndexedSearchParams theSearchParams, boolean theUseOrdinalDatesForDayComparison) { - if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { - stripBaseUrlsFromReferenceParams(theNextAnd); - } - return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token, theUseOrdinalDatesForDayComparison)); - } - - private void stripBaseUrlsFromReferenceParams(List theNextAnd) { - if (myModelConfig.getTreatBaseUrlsAsLocal().isEmpty()) { - return; - } - - for (IQueryParameterType param : theNextAnd) { - ReferenceParam ref = (ReferenceParam) param; - IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null); - - if (dt.hasBaseUrl()) { - if (myModelConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) { - ref.setValue(dt.toUnqualified().getValue()); - } - } - } + private boolean matchParams(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam paramDef, List theNextAnd, ResourceIndexedSearchParams theSearchParams,boolean theUseOrdinalDatesForDayComparison) { + return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theModelConfig, theResourceName, theParamName, paramDef, token, theUseOrdinalDatesForDayComparison)); } private boolean hasChain(List> theAndOrParams) { @@ -256,7 +233,7 @@ public class InMemoryResourceMatcher { for (List theAndOrParam : theAndOrParams) { for (IQueryParameterType param : theAndOrParam) { if (param instanceof BaseParamWithPrefix) { - ParamPrefixEnum prefix = ((BaseParamWithPrefix) param).getPrefix(); + ParamPrefixEnum prefix = ((BaseParamWithPrefix) param).getPrefix(); RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); if (!supportedPrefix(prefix, paramType)) { return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, String.format("The prefix %s is not supported for param type %s", prefix, paramType)); @@ -268,6 +245,7 @@ public class InMemoryResourceMatcher { return InMemoryMatchResult.successfulMatch(); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private boolean supportedPrefix(ParamPrefixEnum theParam, RestSearchParameterTypeEnum theParamType) { if (theParam == null || theParamType == null) { return true; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java index cc0fb9da79a..bf459385ceb 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java @@ -23,31 +23,23 @@ package ca.uhn.fhir.jpa.searchparam.matcher; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service public class IndexedSearchParamExtractor { @Autowired private FhirContext myContext; @Autowired private SearchParamExtractorService mySearchParamExtractorService; - @Autowired - private ResourceLinkExtractor myResourceLinkExtractor; - @Autowired - private InlineResourceLinkResolver myInlineResourceLinkResolver; public ResourceIndexedSearchParams extractIndexedSearchParams(IBaseResource theResource, RequestDetails theRequest) { ResourceTable entity = new ResourceTable(); String resourceType = myContext.getResourceDefinition(theResource).getName(); entity.setResourceType(resourceType); ResourceIndexedSearchParams resourceIndexedSearchParams = new ResourceIndexedSearchParams(); - mySearchParamExtractorService.extractFromResource(theRequest, resourceIndexedSearchParams, entity, theResource); - myResourceLinkExtractor.extractResourceLinks(resourceIndexedSearchParams, entity, theResource, theResource.getMeta().getLastUpdated(), myInlineResourceLinkResolver, false, theRequest); + mySearchParamExtractorService.extractFromResource(null, theRequest, resourceIndexedSearchParams, entity, theResource, theResource.getMeta().getLastUpdated(), false); return resourceIndexedSearchParams; } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java deleted file mode 100644 index 2830a85dd58..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java +++ /dev/null @@ -1,55 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.matcher; - -/*- - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.ForcedId; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.springframework.stereotype.Service; - -@Service -public class InlineResourceLinkResolver implements IResourceLinkResolver { - - @Override - public ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class theType, IBaseReference theReference, RequestDetails theRequest) { - ResourceTable target; - target = new ResourceTable(); - target.setResourceType(theTypeString); - if (theNextId.isIdPartValidLong()) { - target.setId(theNextId.getIdPartAsLong()); - } else { - ForcedId forcedId = new ForcedId(); - forcedId.setForcedId(theNextId.getIdPart()); - target.setForcedId(forcedId); - } - return target; - } - - @Override - public void validateTypeOrThrowException(Class theType) { - // When resolving reference in-memory for a single resource, there's nothing to validate - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java index 4266f361cb7..83385148999 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java @@ -24,9 +24,7 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service public class SearchParamMatcher { @Autowired private IndexedSearchParamExtractor myIndexedSearchParamExtractor; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/Dstu3DistanceHelper.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/Dstu3DistanceHelper.java new file mode 100644 index 00000000000..635586cc8a7 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/util/Dstu3DistanceHelper.java @@ -0,0 +1,105 @@ +package ca.uhn.fhir.jpa.searchparam.util; + +/*- + * #%L + * HAPI FHIR Search Parameters + * %% + * Copyright (C) 2014 - 2020 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.searchparam.SearchParameterMap; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import org.hl7.fhir.dstu3.model.Location; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.Collection; +import java.util.List; + + +/** + * In DSTU3, the near-distance search parameter is separate from near. In this utility method, + * we search for near-distance search parameters and if we find any, remove them from the list + * of search parameters and store it in a dedicated field in {@link SearchParameterMap}. This is so that + * when the "near" search parameter is processed, we have access to this near-distance value. + * This requires at most one near-distance parameter. If more are found, we throw an {@link IllegalArgumentException}. + */ +public class Dstu3DistanceHelper { + public static void setNearDistance(Class theResourceType, SearchParameterMap theParams) { + if (theResourceType == Location.class && theParams.containsKey(Location.SP_NEAR_DISTANCE)) { + List> paramAndList = theParams.get(Location.SP_NEAR_DISTANCE); + QuantityParam quantityParam = getNearDistanceParam(paramAndList); + theParams.setNearDistanceParam(quantityParam); + + // Need to remove near-distance or it we'll get a hashcode predicate for it + theParams.remove(Location.SP_NEAR_DISTANCE); + } else if (theParams.containsKey("location")) { + List> paramAndList = theParams.get("location"); + ReferenceParam referenceParam = getChainedLocationNearDistanceParam(paramAndList); + if (referenceParam != null) { + QuantityParam quantityParam = new QuantityParam(referenceParam.getValue()); + theParams.setNearDistanceParam(quantityParam); + } + } + } + + private static ReferenceParam getChainedLocationNearDistanceParam(List> theParamAndList) { + ReferenceParam retval = null; + List andParamToRemove = null; + for (List paramOrList : theParamAndList) { + IQueryParameterType orParamToRemove = null; + for (IQueryParameterType param : paramOrList) { + if (param instanceof ReferenceParam) { + ReferenceParam referenceParam = (ReferenceParam) param; + if (Location.SP_NEAR_DISTANCE.equals(referenceParam.getChain())) { + if (retval != null) { + throw new IllegalArgumentException("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present"); + } else { + retval = referenceParam; + orParamToRemove = param; + } + } + } + } + if (orParamToRemove != null) { + paramOrList.remove(orParamToRemove); + if (paramOrList.isEmpty()) { + andParamToRemove = paramOrList; + } + } + } + if (andParamToRemove != null) { + theParamAndList.remove(andParamToRemove); + } + return retval; + } + + private static QuantityParam getNearDistanceParam(List> theParamAndList) { + long sum = theParamAndList.stream().mapToLong(Collection::size).sum(); + + // No near-distance Param + if (sum == 0) { + return null; + // A single near-distance Param + } else if (sum == 1) { + return (QuantityParam) theParamAndList.get(0).get(0); + // Too many near-distance params + } else { + throw new IllegalArgumentException("Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present"); + } + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java index 154b8d8b4dd..c440243e102 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java @@ -2,16 +2,15 @@ package ca.uhn.fhir.jpa.searchparam; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.support.IValidationSupport; 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.searchparam.extractor.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.util.StopWatch; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.model.Patient; import org.junit.Test; import org.slf4j.Logger; @@ -23,7 +22,11 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class IndexStressTest { @@ -38,7 +41,8 @@ public class IndexStressTest { FhirContext ctx = FhirContext.forDstu3(); IValidationSupport mockValidationSupport = mock(IValidationSupport.class); - IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), mockValidationSupport)); + when(mockValidationSupport.getFhirContext()).thenReturn(ctx); + IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), mockValidationSupport)); ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class); SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ctx, validationSupport, searchParamRegistry); extractor.start(); @@ -61,6 +65,6 @@ public class IndexStressTest { ourLog.info("Indexed {} times in {}ms/time", loops, sw.getMillisPerOperation(loops)); assertEquals(9, params.size()); - verify(mockValidationSupport, times(1)).fetchAllStructureDefinitions((any(FhirContext.class))); + verify(mockValidationSupport, times(1)).fetchAllStructureDefinitions(); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java index 252834ab82c..9ad2b47360a 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParamsTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor; -import ca.uhn.fhir.jpa.model.entity.ForcedId; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.param.ReferenceParam; @@ -14,72 +14,70 @@ import java.util.Set; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class ResourceIndexedSearchParamsTest { public static final String STRING_ID = "StringId"; public static final String LONG_ID = "123"; private ResourceIndexedSearchParams myParams; - private ResourceTable myTarget; + private ResourceTable mySource; + private ModelConfig myModelConfig = new ModelConfig(); @Before public void before() { - ResourceTable source = new ResourceTable(); - source.setResourceType("Patient"); + mySource = new ResourceTable(); + mySource.setResourceType("Patient"); - myTarget = new ResourceTable(); - myTarget.setResourceType("Organization"); - - myParams = new ResourceIndexedSearchParams(source); - ResourceLink link = new ResourceLink("organization", source, myTarget, new Date()); - myParams.getResourceLinks().add(link); + myParams = new ResourceIndexedSearchParams(mySource); } @Test public void matchResourceLinksStringCompareToLong() { - ReferenceParam referenceParam = getReferenceParam(STRING_ID); - myTarget.setId(123L); + ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date()); + myParams.getResourceLinks().add(link); - boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); + ReferenceParam referenceParam = getReferenceParam(STRING_ID); + boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization"); assertFalse(result); } @Test public void matchResourceLinksStringCompareToString() { - ReferenceParam referenceParam = getReferenceParam(STRING_ID); - ForcedId forcedid = new ForcedId(); - forcedid.setForcedId(STRING_ID); - myTarget.setForcedId(forcedid); + ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date()); + myParams.getResourceLinks().add(link); - boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); + ReferenceParam referenceParam = getReferenceParam(STRING_ID); + boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization"); assertTrue(result); } @Test public void matchResourceLinksLongCompareToString() { - ReferenceParam referenceParam = getReferenceParam(LONG_ID); - ForcedId forcedid = new ForcedId(); - forcedid.setForcedId(STRING_ID); - myTarget.setForcedId(forcedid); + ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date()); + myParams.getResourceLinks().add(link); - boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); + ReferenceParam referenceParam = getReferenceParam(LONG_ID); + boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization"); assertFalse(result); } @Test public void matchResourceLinksLongCompareToLong() { - ReferenceParam referenceParam = getReferenceParam(LONG_ID); - myTarget.setId(123L); + ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date()); + myParams.getResourceLinks().add(link); - boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); + ReferenceParam referenceParam = getReferenceParam(LONG_ID); + boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization"); assertTrue(result); } private ReferenceParam getReferenceParam(String theId) { - ReferenceParam retval = new ReferenceParam(); - retval.setValue(theId); - return retval; + ReferenceParam retVal = new ReferenceParam(); + retVal.setValue(theId); + return retVal; } @@ -93,14 +91,14 @@ public class ResourceIndexedSearchParamsTest { Lists.newArrayList("name=SMITH", "name=JOHN") ); values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices); - assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH")); + assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN", "Patient?gender=male&name=SMITH")); partsChoices = Lists.newArrayList( Lists.newArrayList("gender=male", ""), Lists.newArrayList("name=SMITH", "name=JOHN", "") ); values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices); - assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH")); + assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN", "Patient?gender=male&name=SMITH")); partsChoices = Lists.newArrayList( ); diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java index de75dbb05ea..6a05e7c9280 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java @@ -3,7 +3,18 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; @@ -12,15 +23,23 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Sets; import org.hamcrest.Matchers; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Duration; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.Location; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Questionnaire; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.text.Normalizer; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -40,6 +59,7 @@ public class SearchParamExtractorDstu3Test { ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry(); SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); extractor.start(); Set tokens = extractor.extractSearchParamTokens(obs); assertEquals(1, tokens.size()); @@ -113,9 +133,9 @@ public class SearchParamExtractorDstu3Test { SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry); extractor.start(); - searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE)); - Patient resource = new Patient(); - extractor.extractSearchParamStrings(resource); + searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE)); + Patient resource = new Patient(); + extractor.extractSearchParamStrings(resource); searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", null, RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE)); extractor.extractSearchParamStrings(resource); @@ -144,6 +164,7 @@ public class SearchParamExtractorDstu3Test { MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry(); SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry); + extractor.setPartitionConfigForUnitTest(new PartitionSettings()); extractor.start(); { @@ -281,7 +302,7 @@ public class SearchParamExtractorDstu3Test { @BeforeClass public static void beforeClass() { - ourValidationSupport = new DefaultProfileValidationSupport(); + ourValidationSupport = new DefaultProfileValidationSupport(ourCtx); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java index 85e82cd36ef..e29972743c1 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.*; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseEnumeration; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -12,7 +13,12 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; @@ -33,19 +39,19 @@ public class SearchParamExtractorMegaTest { FhirContext ctx = FhirContext.forDstu2(); ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry(ctx); - process(ctx, new SearchParamExtractorDstu2(ctx, searchParamRegistry)); + process(ctx, new SearchParamExtractorDstu2(ctx, searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings())); ctx = FhirContext.forDstu3(); searchParamRegistry = new MySearchParamRegistry(ctx); - process(ctx, new SearchParamExtractorDstu3(null, ctx, new DefaultProfileValidationSupport(), searchParamRegistry)); + process(ctx, new SearchParamExtractorDstu3(null, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings())); ctx = FhirContext.forR4(); searchParamRegistry = new MySearchParamRegistry(ctx); - process(ctx, new SearchParamExtractorR4(null, ctx, new org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport(), searchParamRegistry)); + process(ctx, new SearchParamExtractorR4(null, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings())); ctx = FhirContext.forR5(); searchParamRegistry = new MySearchParamRegistry(ctx); - process(ctx, new SearchParamExtractorR5(ctx, new org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport(), searchParamRegistry)); + process(ctx, new SearchParamExtractorR5(ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry).setPartitionConfigForUnitTest(new PartitionSettings())); } private void process(FhirContext theCtx, BaseSearchParamExtractor theExtractor) throws Exception { diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java index daf4215ed68..d333fe3889e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcherR5Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.searchparam.matcher; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; @@ -208,7 +209,7 @@ public class InMemoryResourceMatcherR5Test { private ResourceIndexedSearchParams extractDateSearchParam(Observation theObservation) { ResourceIndexedSearchParams retval = new ResourceIndexedSearchParams(); BaseDateTimeType dateValue = (BaseDateTimeType) theObservation.getEffective(); - ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate("Patient", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString()); + ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate(new PartitionSettings(), "Patient", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString()); retval.myDateParams.add(dateParam); return retval; } diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index a7e80f6ec98..d99b38a38c4 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -22,9 +22,14 @@ ca.uhn.hapi.fhir - hapi-fhir-jpaserver-model + hapi-fhir-jpaserver-api ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-model + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-validation @@ -73,6 +78,11 @@ + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + ${project.version} + org.springframework spring-test @@ -124,7 +134,11 @@ awaitility test - + + org.springframework + spring-tx + + diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java similarity index 60% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java index 1f254e919b3..5d17afd3767 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.channel.api; /*- * #%L @@ -20,12 +20,24 @@ package ca.uhn.fhir.jpa.subscription.module.cache; * #L% */ -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import org.hl7.fhir.instance.model.api.IBaseResource; +public class ChannelConsumerSettings { -public interface ISubscriptionProvider { - IBundleProvider search(SearchParameterMap theMap); + /** + * Constructor + */ + public ChannelConsumerSettings() { + super(); + } + + private Integer myConcurrentConsumers; + + public Integer getConcurrentConsumers() { + return myConcurrentConsumers; + } + + public ChannelConsumerSettings setConcurrentConsumers(int theConcurrentConsumers) { + myConcurrentConsumers = theConcurrentConsumers; + return this; + } - boolean loadSubscription(IBaseResource theResource); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java new file mode 100644 index 00000000000..0da00876de6 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java @@ -0,0 +1,67 @@ +package ca.uhn.fhir.jpa.subscription.channel.api; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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% + */ + +/** + * This interface is the factory for Queue Channels, which are the low level abstraction over a + * queue (e.g. memory queue, JMS queue, Kafka stream, etc.) for any purpose. + */ +public interface IChannelFactory { + + /** + * Create a channel that is used to receive messages from the queue. + * + *

        + * Implementations can choose to return the same object for multiple invocations of this method (and {@link #getOrCreateReceiver(String, Class, ChannelConsumerSettings)} + * when invoked with the same {@literal theChannelName} if they need to, or they can create a new instance. + *

        + * + * @param theChannelName The actual underlying queue name + * @param theMessageType The object type that will be placed on this queue. Objects will be Jackson-annotated structures. + * @param theConfig Contains the configuration for subscribers. Note that this parameter is provided for + * both {@link #getOrCreateReceiver} and + * {@link #getOrCreateProducer(String, Class, ChannelConsumerSettings)} + * even though this object is used to configure the sender only. We do this because the factory + * may want to create a single object to be used for both the sender and receiver, so this allows + * the config details to be known regardless of which method is returned first. + */ + IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig); + + /** + * Create a channel that is used to send messages to the queue. + * + *

        + * Implementations can choose to return the same object for multiple invocations of this method (and {@link #getOrCreateReceiver(String, Class, ChannelConsumerSettings)} + * when invoked with the same {@literal theChannelName} if they need to, or they can create a new instance. + *

        + * + * @param theChannelName The actual underlying queue name + * @param theMessageType The object type that will be placed on this queue. Objects will be Jackson-annotated structures. + * @param theConfig Contains the configuration for subscribers. Note that this parameter is provided for + * both {@link #getOrCreateReceiver} and + * {@link #getOrCreateProducer(String, Class, ChannelConsumerSettings)} + * even though this object is used to configure the sender only. We do this because the factory + * may want to create a single object to be used for both the sender and receiver, so this allows + * the config details to be known regardless of which method is returned first. + */ + IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig); + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelProducer.java similarity index 73% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelProducer.java index 939fd5f1bfc..d76d21f9d6e 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelProducer.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.channel.api; /*- * #%L @@ -20,9 +20,8 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.InterceptableChannel; -public interface IResourceRetriever { - IBaseResource getResource(IIdType theId); +public interface IChannelProducer extends MessageChannel, InterceptableChannel { } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscribableChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java similarity index 70% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscribableChannelFactory.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java index 669401726d7..4c3677c4760 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscribableChannelFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.api; /*- * #%L @@ -21,11 +21,7 @@ package ca.uhn.fhir.jpa.subscription.module.channel; */ import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.InterceptableChannel; -public interface ISubscribableChannelFactory { - SubscribableChannel createSubscribableChannel(String theChannelName, Class theMessageType, int theConcurrentConsumers); - - int getDeliveryChannelConcurrentConsumers(); - - int getMatchingChannelConcurrentConsumers(); +public interface IChannelReceiver extends SubscribableChannel, InterceptableChannel { } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java new file mode 100644 index 00000000000..a56bd4f4968 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java @@ -0,0 +1,45 @@ +package ca.uhn.fhir.jpa.subscription.channel.config; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SubscriptionChannelConfig { + + /** + * Create a @Primary @Bean if you need a different implementation + */ + @Bean + public IChannelFactory queueChannelFactory() { + return new LinkedBlockingChannelFactory(); + } + + @Bean + public SubscriptionChannelFactory subscriptionChannelFactory(IChannelFactory theQueueChannelFactory) { + return new SubscriptionChannelFactory(theQueueChannelFactory); + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java new file mode 100644 index 00000000000..5c5565de963 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java @@ -0,0 +1,48 @@ +package ca.uhn.fhir.jpa.subscription.channel.impl; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import org.springframework.messaging.support.ExecutorSubscribableChannel; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +public class LinkedBlockingChannel extends ExecutorSubscribableChannel implements IChannelProducer, IChannelReceiver { + + private final BlockingQueue myQueue; + + public LinkedBlockingChannel(ThreadPoolExecutor theExecutor, BlockingQueue theQueue) { + super(theExecutor); + myQueue = theQueue; + } + + public int getQueueSizeForUnitTest() { + return myQueue.size(); + } + + public void clearInterceptorsForUnitTest() { + while (getInterceptors().size() > 0) { + removeInterceptor(0); + } + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java new file mode 100644 index 00000000000..64840beace8 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java @@ -0,0 +1,109 @@ +package ca.uhn.fhir.jpa.subscription.channel.impl; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; +import ca.uhn.fhir.util.StopWatch; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class LinkedBlockingChannelFactory implements IChannelFactory { + + private static final Logger ourLog = LoggerFactory.getLogger(LinkedBlockingChannelFactory.class); + private Map myChannels = Collections.synchronizedMap(new HashMap<>()); + + /** + * Constructor + */ + public LinkedBlockingChannelFactory() { + super(); + } + + @Override + public IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig) { + return getOrCreateChannel(theChannelName, theConfig.getConcurrentConsumers()); + } + + @Override + public IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig) { + return getOrCreateChannel(theChannelName, theConfig.getConcurrentConsumers()); + } + + private LinkedBlockingChannel getOrCreateChannel(String theChannelName, int theConcurrentConsumers) { + return myChannels.computeIfAbsent(theChannelName, t -> { + + String threadNamingPattern = theChannelName + "-%d"; + + ThreadFactory threadFactory = new BasicThreadFactory.Builder() + .namingPattern(threadNamingPattern) + .daemon(false) + .priority(Thread.NORM_PRIORITY) + .build(); + + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE); + RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> { + ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", queue.size()); + StopWatch sw = new StopWatch(); + try { + queue.put(theRunnable); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RejectedExecutionException("Task " + theRunnable.toString() + + " rejected from " + e.toString()); + } + ourLog.info("Slot become available after {}ms", sw.getMillis()); + }; + ThreadPoolExecutor executor = new ThreadPoolExecutor( + 1, + theConcurrentConsumers, + 0L, + TimeUnit.MILLISECONDS, + queue, + threadFactory, + rejectedExecutionHandler); + return new LinkedBlockingChannel(executor, queue); + + }); + } + + + @PreDestroy + public void stop() { + myChannels.clear(); + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscriptionDeliveryChannelNamer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/ISubscriptionDeliveryChannelNamer.java similarity index 86% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscriptionDeliveryChannelNamer.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/ISubscriptionDeliveryChannelNamer.java index bf7700db087..4c02e60b056 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/ISubscriptionDeliveryChannelNamer.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/ISubscriptionDeliveryChannelNamer.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; public interface ISubscriptionDeliveryChannelNamer { String nameFromSubscription(CanonicalSubscription theCanonicalSubscription); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelCache.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelCache.java similarity index 93% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelCache.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelCache.java index 5f8f66983ff..7cb9034c858 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelCache.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelCache.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java new file mode 100644 index 00000000000..57494d9d569 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java @@ -0,0 +1,128 @@ +package ca.uhn.fhir.jpa.subscription.channel.subscription; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; +import org.apache.commons.lang3.Validate; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.AbstractSubscribableChannel; +import org.springframework.messaging.support.ChannelInterceptor; + +public class SubscriptionChannelFactory { + + private final IChannelFactory myQueueChannelFactory; + + /** + * Constructor + */ + public SubscriptionChannelFactory(IChannelFactory theQueueChannelFactory) { + Validate.notNull(theQueueChannelFactory); + myQueueChannelFactory = theQueueChannelFactory; + } + + public IChannelProducer newDeliverySendingChannel(String theChannelName, ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = newConfigForDeliveryChannel(theOptions); + return myQueueChannelFactory.getOrCreateProducer(theChannelName, ResourceDeliveryJsonMessage.class, config); + } + + public IChannelReceiver newDeliveryReceivingChannel(String theChannelName, ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = newConfigForDeliveryChannel(theOptions); + IChannelReceiver channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceDeliveryJsonMessage.class, config); + return new BroadcastingSubscribableChannelWrapper(channel); + } + + public IChannelProducer newMatchingSendingChannel(String theChannelName, ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = newConfigForMatchingChannel(theOptions); + return myQueueChannelFactory.getOrCreateProducer(theChannelName, ResourceModifiedJsonMessage.class, config); + } + + public IChannelReceiver newMatchingReceivingChannel(String theChannelName, ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = newConfigForMatchingChannel(theOptions); + IChannelReceiver channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceModifiedJsonMessage.class, config); + return new BroadcastingSubscribableChannelWrapper(channel); + } + + protected ChannelConsumerSettings newConfigForDeliveryChannel(ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = new ChannelConsumerSettings(); + config.setConcurrentConsumers(getDeliveryChannelConcurrentConsumers()); + return config; + } + + protected ChannelConsumerSettings newConfigForMatchingChannel(ChannelConsumerSettings theOptions) { + ChannelConsumerSettings config = new ChannelConsumerSettings(); + config.setConcurrentConsumers(getMatchingChannelConcurrentConsumers()); + return config; + } + + public int getDeliveryChannelConcurrentConsumers() { + return SubscriptionConstants.DELIVERY_CHANNEL_CONCURRENT_CONSUMERS; + } + + public int getMatchingChannelConcurrentConsumers() { + return SubscriptionConstants.MATCHING_CHANNEL_CONCURRENT_CONSUMERS; + } + + public static class BroadcastingSubscribableChannelWrapper extends AbstractSubscribableChannel implements IChannelReceiver, DisposableBean { + + private final IChannelReceiver myWrappedChannel; + + public BroadcastingSubscribableChannelWrapper(IChannelReceiver theChannel) { + theChannel.subscribe(message -> send(message)); + myWrappedChannel = theChannel; + } + + public SubscribableChannel getWrappedChannel() { + return myWrappedChannel; + } + + @Override + protected boolean sendInternal(Message theMessage, long timeout) { + for (MessageHandler next : getSubscribers()) { + next.handleMessage(theMessage); + } + return true; + } + + @Override + public void destroy() throws Exception { + if (myWrappedChannel instanceof DisposableBean) { + ((DisposableBean) myWrappedChannel).destroy(); + } + } + + @Override + public void addInterceptor(ChannelInterceptor interceptor) { + super.addInterceptor(interceptor); + myWrappedChannel.addInterceptor(interceptor); + } + + + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistry.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelRegistry.java similarity index 56% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistry.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelRegistry.java index 8dbdfffba09..02ffc05022b 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistry.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelRegistry.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,100 +20,94 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import com.google.common.annotations.VisibleForTesting; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.stereotype.Component; -import java.util.Collection; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; -@Component public class SubscriptionChannelRegistry { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRegistry.class); - private final SubscriptionChannelCache mySubscriptionChannelCache = new SubscriptionChannelCache(); + private final SubscriptionChannelCache myDeliveryReceiverChannels = new SubscriptionChannelCache(); // This map is a reference count so we know to destroy the channel when there are no more active subscriptions using it // Key Channel Name, Value Subscription Id private final Multimap myActiveSubscriptionByChannelName = MultimapBuilder.hashKeys().arrayListValues().build(); + private final Map myChannelNameToSender = new ConcurrentHashMap<>(); @Autowired private SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory; @Autowired private SubscriptionChannelFactory mySubscriptionDeliveryChannelFactory; - @Autowired - private ModelConfig myModelConfig; public synchronized void add(ActiveSubscription theActiveSubscription) { - if (!myModelConfig.isSubscriptionMatchingEnabled()) { - return; - } String channelName = theActiveSubscription.getChannelName(); ourLog.info("Adding subscription {} to channel {}", theActiveSubscription.getId(), channelName); myActiveSubscriptionByChannelName.put(channelName, theActiveSubscription.getId()); - if (mySubscriptionChannelCache.containsKey(channelName)) { + if (myDeliveryReceiverChannels.containsKey(channelName)) { ourLog.info("Channel {} already exists. Not creating.", channelName); return; } - SubscribableChannel deliveryChannel; - Optional deliveryHandler; + IChannelReceiver channelReceiver = newReceivingChannel(channelName); + Optional deliveryHandler = mySubscriptionDeliveryHandlerFactory.createDeliveryHandler(theActiveSubscription.getChannelType()); - deliveryChannel = mySubscriptionDeliveryChannelFactory.newDeliveryChannel(channelName); - deliveryHandler = mySubscriptionDeliveryHandlerFactory.createDeliveryHandler(theActiveSubscription.getChannelType()); - - SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = new SubscriptionChannelWithHandlers(channelName, deliveryChannel); + SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = new SubscriptionChannelWithHandlers(channelName, channelReceiver); deliveryHandler.ifPresent(subscriptionChannelWithHandlers::addHandler); - mySubscriptionChannelCache.put(channelName, subscriptionChannelWithHandlers); + myDeliveryReceiverChannels.put(channelName, subscriptionChannelWithHandlers); + + IChannelProducer sendingChannel = newSendingChannel(channelName); + myChannelNameToSender.put(channelName, sendingChannel); + } + + protected IChannelReceiver newReceivingChannel(String theChannelName) { + return mySubscriptionDeliveryChannelFactory.newDeliveryReceivingChannel(theChannelName, null); + } + + protected IChannelProducer newSendingChannel(String theChannelName) { + return mySubscriptionDeliveryChannelFactory.newDeliverySendingChannel(theChannelName, null); } public synchronized void remove(ActiveSubscription theActiveSubscription) { - if (!myModelConfig.isSubscriptionMatchingEnabled()) { - return; - } String channelName = theActiveSubscription.getChannelName(); - ourLog.info("Removing subscription {} from channel {}", theActiveSubscription.getId() ,channelName); + ourLog.info("Removing subscription {} from channel {}", theActiveSubscription.getId(), channelName); boolean removed = myActiveSubscriptionByChannelName.remove(channelName, theActiveSubscription.getId()); if (!removed) { - ourLog.warn("Failed to remove subscription {} from channel {}", theActiveSubscription.getId() ,channelName); + ourLog.warn("Failed to remove subscription {} from channel {}", theActiveSubscription.getId(), channelName); } // This was the last one. Close and remove the channel if (!myActiveSubscriptionByChannelName.containsKey(channelName)) { - SubscriptionChannelWithHandlers channel = mySubscriptionChannelCache.get(channelName); + SubscriptionChannelWithHandlers channel = myDeliveryReceiverChannels.get(channelName); if (channel != null) { channel.close(); } - mySubscriptionChannelCache.closeAndRemove(channelName); + myDeliveryReceiverChannels.closeAndRemove(channelName); + myChannelNameToSender.remove(channelName); } + } - public synchronized SubscriptionChannelWithHandlers get(String theChannelName) { - return mySubscriptionChannelCache.get(theChannelName); + public synchronized SubscriptionChannelWithHandlers getDeliveryReceiverChannel(String theChannelName) { + return myDeliveryReceiverChannels.get(theChannelName); + } + + public synchronized MessageChannel getDeliverySenderChannel(String theChannelName) { + return myChannelNameToSender.get(theChannelName); } public synchronized int size() { - return mySubscriptionChannelCache.size(); - } - - @VisibleForTesting - public void logForUnitTest() { - ourLog.info("{} Channels: {}", this, size()); - mySubscriptionChannelCache.logForUnitTest(); - for (String key : myActiveSubscriptionByChannelName.keySet()) { - Collection list = myActiveSubscriptionByChannelName.get(key); - for (String value : list) { - ourLog.info("ActiveSubscriptionByChannelName {}: {}", key, value); - } - } + return myDeliveryReceiverChannels.size(); } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelWithHandlers.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelWithHandlers.java similarity index 95% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelWithHandlers.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelWithHandlers.java index 4e6577d4eba..a48c4c7a7dc 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelWithHandlers.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelWithHandlers.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryChannelNamer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryChannelNamer.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryChannelNamer.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryChannelNamer.java index d3a1caca889..42764199a8e 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryChannelNamer.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryChannelNamer.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,10 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import org.springframework.stereotype.Service; -@Service public class SubscriptionDeliveryChannelNamer implements ISubscriptionDeliveryChannelNamer { @Override public String nameFromSubscription(CanonicalSubscription theCanonicalSubscription) { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java similarity index 50% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java index 693d1ec994f..15cd802c493 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; +package ca.uhn.fhir.jpa.subscription.channel.subscription; /*- * #%L @@ -20,30 +20,35 @@ package ca.uhn.fhir.jpa.subscription.module.channel; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; -import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber; -import org.springframework.beans.factory.annotation.Lookup; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.SubscriptionDeliveringEmailSubscriber; +import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.messaging.MessageHandler; -import org.springframework.stereotype.Component; import java.util.Optional; -@Component -public abstract class SubscriptionDeliveryHandlerFactory { +public class SubscriptionDeliveryHandlerFactory { private IEmailSender myEmailSender; - @Lookup - protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender); - @Lookup - protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber(); + @Autowired + private ApplicationContext myApplicationContext; + + protected SubscriptionDeliveringEmailSubscriber newSubscriptionDeliveringEmailSubscriber(IEmailSender theEmailSender) { + return myApplicationContext.getBean(SubscriptionDeliveringEmailSubscriber.class, theEmailSender); + } + + protected SubscriptionDeliveringRestHookSubscriber newSubscriptionDeliveringRestHookSubscriber() { + return myApplicationContext.getBean(SubscriptionDeliveringRestHookSubscriber.class); + } public Optional createDeliveryHandler(CanonicalSubscriptionChannelType theChannelType) { if (theChannelType == CanonicalSubscriptionChannelType.EMAIL) { - return Optional.of(getSubscriptionDeliveringEmailSubscriber(myEmailSender)); + return Optional.of(newSubscriptionDeliveringEmailSubscriber(myEmailSender)); } else if (theChannelType == CanonicalSubscriptionChannelType.RESTHOOK) { - return Optional.of(getSubscriptionDeliveringRestHookSubscriber()); + return Optional.of(newSubscriptionDeliveringRestHookSubscriber()); } else { return Optional.empty(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java new file mode 100644 index 00000000000..f04c2fc600a --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java @@ -0,0 +1,125 @@ +package ca.uhn.fhir.jpa.subscription.match.config; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryChannelNamer; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.match.deliver.email.SubscriptionDeliveringEmailSubscriber; +import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.CompositeInMemoryDaoSubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.DaoSubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.MatchingQueueSubscriberLoader; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionActivatingSubscriber; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionRegisteringSubscriber; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.config.SubscriptionModelConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; + +/** + * This Spring config should be imported by a system that pulls messages off of the + * matching queue for processing, and handles delivery + */ +@Import(SubscriptionModelConfig.class) +public class SubscriptionProcessorConfig { + + @Bean + public SubscriptionMatchingSubscriber subscriptionMatchingSubscriber() { + return new SubscriptionMatchingSubscriber(); + } + + @Bean + public SubscriptionActivatingSubscriber subscriptionActivatingSubscriber() { + return new SubscriptionActivatingSubscriber(); + } + + @Bean + public MatchingQueueSubscriberLoader subscriptionMatchingSubscriberLoader() { + return new MatchingQueueSubscriberLoader(); + } + + @Bean + public SubscriptionRegisteringSubscriber subscriptionRegisteringSubscriber() { + return new SubscriptionRegisteringSubscriber(); + } + + @Bean + public SubscriptionRegistry subscriptionRegistry() { + return new SubscriptionRegistry(); + } + + @Bean + public SubscriptionDeliveryChannelNamer subscriptionDeliveryChannelNamer() { + return new SubscriptionDeliveryChannelNamer(); + } + + @Bean + public SubscriptionLoader subscriptionLoader() { + return new SubscriptionLoader(); + } + + @Bean + public SubscriptionChannelRegistry subscriptionChannelRegistry() { + return new SubscriptionChannelRegistry(); + } + + @Bean + public SubscriptionDeliveryHandlerFactory subscriptionDeliveryHandlerFactory() { + return new SubscriptionDeliveryHandlerFactory(); + } + + @Bean + @Scope("prototype") + public SubscriptionDeliveringRestHookSubscriber subscriptionDeliveringRestHookSubscriber() { + return new SubscriptionDeliveringRestHookSubscriber(); + } + + @Bean + @Scope("prototype") + public SubscriptionDeliveringEmailSubscriber subscriptionDeliveringEmailSubscriber(IEmailSender theEmailSender) { + return new SubscriptionDeliveringEmailSubscriber(theEmailSender); + } + + @Bean + public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() { + return new InMemorySubscriptionMatcher(); + } + + @Bean + public DaoSubscriptionMatcher daoSubscriptionMatcher() { + return new DaoSubscriptionMatcher(); + } + + @Bean + @Primary + public ISubscriptionMatcher subscriptionMatcher(DaoSubscriptionMatcher theDaoSubscriptionMatcher, InMemorySubscriptionMatcher theInMemorySubscriptionMatcher) { + return new CompositeInMemoryDaoSubscriptionMatcher(theDaoSubscriptionMatcher, theInMemorySubscriptionMatcher); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/WebsocketDispatcherConfig.java similarity index 83% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/WebsocketDispatcherConfig.java index 92feb2e4b01..cbbbef5fe88 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/WebsocketDispatcherConfig.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.config; +package ca.uhn.fhir.jpa.subscription.match.config; /* * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.config; */ import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.subscription.module.subscriber.websocket.SubscriptionWebsocketHandler; +import ca.uhn.fhir.jpa.subscription.match.deliver.websocket.SubscriptionWebsocketHandler; +import ca.uhn.fhir.jpa.subscription.match.deliver.websocket.WebsocketConnectionValidator; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -37,9 +38,15 @@ import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; @EnableWebSocket() @Controller public class WebsocketDispatcherConfig implements WebSocketConfigurer { + @Autowired ModelConfig myModelConfig; + @Bean + public WebsocketConnectionValidator websocketConnectionValidator() { + return new WebsocketConnectionValidator(); + } + @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) { theRegistry.addHandler(subscriptionWebSocketHandler(), myModelConfig.getWebsocketContextPath()).setAllowedOrigins("*"); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriber.java similarity index 86% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriber.java index 0107adf61ad..a9c59b8376f 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.match.deliver; /*- * #%L @@ -24,9 +24,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,17 +100,17 @@ public abstract class BaseSubscriptionDeliverySubscriber implements MessageHandl public abstract void handleMessage(ResourceDeliveryMessage theMessage) throws Exception; @VisibleForTesting - void setFhirContextForUnitTest(FhirContext theCtx) { + public void setFhirContextForUnitTest(FhirContext theCtx) { myFhirContext = theCtx; } @VisibleForTesting - void setInterceptorBroadcasterForUnitTest(IInterceptorBroadcaster theInterceptorBroadcaster) { + public void setInterceptorBroadcasterForUnitTest(IInterceptorBroadcaster theInterceptorBroadcaster) { myInterceptorBroadcaster = theInterceptorBroadcaster; } @VisibleForTesting - void setSubscriptionRegistryForUnitTest(SubscriptionRegistry theSubscriptionRegistry) { + public void setSubscriptionRegistryForUnitTest(SubscriptionRegistry theSubscriptionRegistry) { mySubscriptionRegistry = theSubscriptionRegistry; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/EmailDetails.java similarity index 96% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/EmailDetails.java index 4043255b58c..bb1c2cfcfb1 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/EmailDetails.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.email; +package ca.uhn.fhir.jpa.subscription.match.deliver.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/IEmailSender.java similarity index 92% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/IEmailSender.java index ff6d3a9dafb..a327d3aac76 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/IEmailSender.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.email; +package ca.uhn.fhir.jpa.subscription.match.deliver.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/JavaMailEmailSender.java similarity index 98% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/JavaMailEmailSender.java index df826fa80ff..feb670436ba 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/JavaMailEmailSender.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.email; +package ca.uhn.fhir.jpa.subscription.match.deliver.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/SubscriptionDeliveringEmailSubscriber.java similarity index 89% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/SubscriptionDeliveringEmailSubscriber.java index 79175fd38c6..b1677b114d8 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/email/SubscriptionDeliveringEmailSubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.email; +package ca.uhn.fhir.jpa.subscription.match.deliver.email; /*- * #%L @@ -22,24 +22,21 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.email; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseSubscriptionDeliverySubscriber; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.match.deliver.BaseSubscriptionDeliverySubscriber; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import ca.uhn.fhir.rest.api.EncodingEnum; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import static org.apache.commons.lang3.StringUtils.*; -@Component -@Scope("prototype") public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliverySubscriber { private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringEmailSubscriber.class); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java similarity index 84% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java index b5e3d84170a..ad82286d5a5 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.match.deliver.resthook; /*- * #%L @@ -20,13 +20,22 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ +import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.interceptor.api.HookParams; -import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.subscription.match.deliver.BaseSubscriptionDeliverySubscriber; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; -import ca.uhn.fhir.rest.client.api.*; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; @@ -39,20 +48,32 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.messaging.MessagingException; -import org.springframework.stereotype.Component; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static org.apache.commons.lang3.StringUtils.isNotBlank; -@Component @Scope("prototype") public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber { + @Autowired - IResourceRetriever myResourceRetriever; + private DaoRegistry myDaoRegistry; + private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class); + + /** + * Constructor + */ + public SubscriptionDeliveringRestHookSubscriber() { + super(); + } + protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) { IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription); @@ -108,6 +129,13 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } } + public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException { + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType()); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceDef.getImplementingClass()); + return dao.read(payloadId.toVersionless()); + } + + protected IBaseResource getAndMassagePayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription) { IBaseResource payloadResource = theMsg.getPayload(myFhirContext); @@ -116,7 +144,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe try { if (payloadId != null) { - payloadResource = myResourceRetriever.getResource(payloadId.toVersionless()); + payloadResource = getResource(payloadId.toVersionless()); } else { return null; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java index 66900c5c271..dbefcf7249b 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; +package ca.uhn.fhir.jpa.subscription.match.deliver.websocket; /* * #%L @@ -21,10 +21,10 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelWithHandlers; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelWithHandlers; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.IdType; import org.slf4j.Logger; @@ -50,6 +50,13 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement @Autowired SubscriptionChannelRegistry mySubscriptionChannelRegistry; + /** + * Constructor + */ + public SubscriptionWebsocketHandler() { + super(); + } + @Autowired private FhirContext myCtx; @@ -115,13 +122,13 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement mySession = theSession; myActiveSubscription = theActiveSubscription; - SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.get(theActiveSubscription.getChannelName()); + SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.getDeliveryReceiverChannel(theActiveSubscription.getChannelName()); subscriptionChannelWithHandlers.addHandler(this); } @Override public void closing() { - SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.get(myActiveSubscription.getChannelName()); + SubscriptionChannelWithHandlers subscriptionChannelWithHandlers = mySubscriptionChannelRegistry.getDeliveryReceiverChannel(myActiveSubscription.getChannelName()); subscriptionChannelWithHandlers.removeHandler(this); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketConnectionValidator.java similarity index 84% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketConnectionValidator.java index 85ed070204e..a454eafba10 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketConnectionValidator.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; +package ca.uhn.fhir.jpa.subscription.match.deliver.websocket; /*- * #%L @@ -20,17 +20,15 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import com.sun.istack.NotNull; import org.hl7.fhir.r4.model.IdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service public class WebsocketConnectionValidator { private static Logger ourLog = LoggerFactory.getLogger(WebsocketConnectionValidator.class); @@ -38,6 +36,13 @@ public class WebsocketConnectionValidator { SubscriptionRegistry mySubscriptionRegistry; + /** + * Constructor + */ + public WebsocketConnectionValidator() { + super(); + } + public WebsocketValidationResponse validate(@NotNull IdType id) { if (!id.hasIdPart() || !id.isIdPartValid()) { return WebsocketValidationResponse.INVALID_RESPONSE("Invalid bind request - No ID included: " + id.getValue()); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketValidationResponse.java similarity index 92% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketValidationResponse.java index b0112a83ee5..355ca0b7066 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/WebsocketValidationResponse.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; +package ca.uhn.fhir.jpa.subscription.match.deliver.websocket; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; public class WebsocketValidationResponse { private final boolean myValid; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java similarity index 85% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java index 6d885a07416..5b16a5400b2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription.dbmatcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,12 +20,10 @@ package ca.uhn.fhir.jpa.subscription.dbmatcher; * #L% */ -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; -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.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcher.java similarity index 76% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcher.java index b8bcb6dd972..35e0d01e3d9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcher.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription.dbmatcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -22,23 +22,19 @@ package ca.uhn.fhir.jpa.subscription.dbmatcher; 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.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; -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.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.rest.api.server.IBundleProvider; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; public class DaoSubscriptionMatcher implements ISubscriptionMatcher { @Autowired @@ -48,8 +44,6 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher { private Logger ourLog = LoggerFactory.getLogger(DaoSubscriptionMatcher.class); @Autowired private FhirContext myCtx; - @Autowired - private PlatformTransactionManager myTxManager; @Override public InMemoryMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { @@ -79,10 +73,7 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher { IFhirResourceDao responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass()); responseCriteriaUrl.setLoadSynchronousUpTo(1); - TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); - txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - return txTemplate.execute(t -> responseDao.search(responseCriteriaUrl)); - + return responseDao.search(responseCriteriaUrl); } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/IResourceModifiedConsumer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/IResourceModifiedConsumer.java new file mode 100644 index 00000000000..e02a5bdd82d --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/IResourceModifiedConsumer.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.model.ResourceModifiedMessage; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IBaseResource; + +public interface IResourceModifiedConsumer { + + /** + * This is an internal API - Use with caution! + */ + void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType, RequestDetails theRequest); + + /** + * This is an internal API - Use with caution! + */ + void submitResourceModified(ResourceModifiedMessage theMsg); + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/ISubscriptionMatcher.java similarity index 82% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/ISubscriptionMatcher.java index 15337a26e21..0880bbadfb9 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/ISubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L @@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; */ import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; public interface ISubscriptionMatcher { InMemoryMatchResult match(CanonicalSubscription subscription, ResourceModifiedMessage msg); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/InMemorySubscriptionMatcher.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/InMemorySubscriptionMatcher.java index ca6d3d8e0dd..5eb0d304e9b 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/InMemorySubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L @@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchingStrategy.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionMatchingStrategy.java similarity index 94% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchingStrategy.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionMatchingStrategy.java index f49b7996b63..b8f9e034d43 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchingStrategy.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionMatchingStrategy.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java index 3c805b537b0..8075ae877c1 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; /*- * #%L @@ -23,14 +23,19 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service public class SubscriptionStrategyEvaluator { @Autowired private InMemoryResourceMatcher myInMemoryResourceMatcher; + /** + * Constructor + */ + public SubscriptionStrategyEvaluator() { + super(); + } + public SubscriptionMatchingStrategy determineStrategy(String theCriteria) { InMemoryMatchResult result = myInMemoryResourceMatcher.match(theCriteria, null, null); if (result.supported()) { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java new file mode 100644 index 00000000000..1fec3659b80 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.MessageHandler; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public abstract class BaseSubscriberForSubscriptionResources implements MessageHandler { + + @Autowired + protected FhirContext myFhirContext; + + protected boolean isSubscription(ResourceModifiedMessage theNewResource) { + String payloadIdType = null; + IIdType payloadId = theNewResource.getId(myFhirContext); + if (payloadId != null) { + payloadIdType = payloadId.getResourceType(); + } + if (isBlank(payloadIdType)) { + IBaseResource payload = theNewResource.getNewPayload(myFhirContext); + if (payload != null) { + payloadIdType = myFhirContext.getResourceDefinition(payload).getName(); + } + } + + return ResourceTypeEnum.SUBSCRIPTION.getCode().equals(payloadIdType); + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/MatchingQueueSubscriberLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/MatchingQueueSubscriberLoader.java new file mode 100644 index 00000000000..8c3d63a1841 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/MatchingQueueSubscriberLoader.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; + +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.SubscribableChannel; + +import javax.annotation.PreDestroy; + +import static ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber.SUBSCRIPTION_MATCHING_CHANNEL_NAME; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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 class MatchingQueueSubscriberLoader { + private Logger ourLog = LoggerFactory.getLogger(MatchingQueueSubscriberLoader.class); + + @Autowired + private SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; + @Autowired + private SubscriptionChannelFactory mySubscriptionChannelFactory; + @Autowired + private SubscriptionRegisteringSubscriber mySubscriptionRegisteringSubscriber; + @Autowired + private SubscriptionActivatingSubscriber mySubscriptionActivatingSubscriber; + + protected SubscribableChannel myMatchingChannel; + + @EventListener(classes = {ContextRefreshedEvent.class}) + public void handleContextRefreshEvent() { + if (myMatchingChannel == null) { + myMatchingChannel = mySubscriptionChannelFactory.newMatchingReceivingChannel(SUBSCRIPTION_MATCHING_CHANNEL_NAME, null); + } + if (myMatchingChannel != null) { + myMatchingChannel.subscribe(mySubscriptionMatchingSubscriber); + myMatchingChannel.subscribe(mySubscriptionActivatingSubscriber); + myMatchingChannel.subscribe(mySubscriptionRegisteringSubscriber); + ourLog.info("Subscription Matching Subscriber subscribed to Matching Channel {} with name {}", myMatchingChannel.getClass().getName(), SUBSCRIPTION_MATCHING_CHANNEL_NAME); + } + } + + @SuppressWarnings("unused") + @PreDestroy + public void stop() { + if (myMatchingChannel != null) { + myMatchingChannel.unsubscribe(mySubscriptionMatchingSubscriber); + myMatchingChannel.unsubscribe(mySubscriptionActivatingSubscriber); + myMatchingChannel.unsubscribe(mySubscriptionRegisteringSubscriber); + } + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionActivatingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionActivatingSubscriber.java new file mode 100644 index 00000000000..652f979d489 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionActivatingSubscriber.java @@ -0,0 +1,135 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.SubscriptionUtil; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; + +import javax.annotation.Nonnull; + +/** + * Responsible for transitioning subscription resources from REQUESTED to ACTIVE + * Once activated, the subscription is added to the SubscriptionRegistry. + *

        + * Also validates criteria. If invalid, rejects the subscription without persisting the subscription. + */ +public class SubscriptionActivatingSubscriber extends BaseSubscriberForSubscriptionResources implements MessageHandler { + private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class); + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private SubscriptionCanonicalizer mySubscriptionCanonicalizer; + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; + + /** + * Constructor + */ + public SubscriptionActivatingSubscriber() { + super(); + } + + @Override + public void handleMessage(@Nonnull Message theMessage) throws MessagingException { + if (!(theMessage instanceof ResourceModifiedJsonMessage)) { + ourLog.warn("Received message of unexpected type on matching channel: {}", theMessage); + return; + } + + ResourceModifiedMessage payload = ((ResourceModifiedJsonMessage) theMessage).getPayload(); + if (!isSubscription(payload)) { + return; + } + + switch (payload.getOperationType()) { + case CREATE: + case UPDATE: + activateOrRegisterSubscriptionIfRequired(payload.getNewPayload(myFhirContext)); + break; + case DELETE: + case MANUALLY_TRIGGERED: + default: + break; + } + + } + + public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { + // Grab the value for "Subscription.channel.type" so we can see if this + // subscriber applies.. + CanonicalSubscriptionChannelType subscriptionChannelType = mySubscriptionCanonicalizer.getChannelType(theSubscription); + + // Only activate supported subscriptions + if (subscriptionChannelType == null || !myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType.toCanonical())) { + return false; + } + + String statusString = mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription); + + if (SubscriptionConstants.REQUESTED_STATUS.equals(statusString)) { + return activateSubscription(theSubscription); + } + + return false; + } + + @SuppressWarnings("unchecked") + private boolean activateSubscription(final IBaseResource theSubscription) { + 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(), SubscriptionConstants.REQUESTED_STATUS, SubscriptionConstants.ACTIVE_STATUS); + try { + SubscriptionUtil.setStatus(myFhirContext, subscription, SubscriptionConstants.ACTIVE_STATUS); + subscriptionDao.update(subscription); + return true; + } catch (final UnprocessableEntityException e) { + ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); + SubscriptionUtil.setStatus(myFhirContext, subscription, "error"); + SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage()); + subscriptionDao.update(subscription); + return false; + } + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java similarity index 87% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java index 0401be75496..fe96e8f0340 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java @@ -1,17 +1,19 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; -import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.rest.api.EncodingEnum; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -23,8 +25,8 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; -import org.springframework.stereotype.Service; +import javax.annotation.Nonnull; import java.util.Collection; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; @@ -50,9 +52,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -@Service public class SubscriptionMatchingSubscriber implements MessageHandler { private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatchingSubscriber.class); + public static final String SUBSCRIPTION_MATCHING_CHANNEL_NAME = "subscription-matching"; @Autowired private ISubscriptionMatcher mySubscriptionMatcher; @@ -65,8 +67,16 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { @Autowired private SubscriptionChannelRegistry mySubscriptionChannelRegistry; + /** + * Constructor + */ + public SubscriptionMatchingSubscriber() { + super(); + } + + @Override - public void handleMessage(Message theMessage) throws MessagingException { + public void handleMessage(@Nonnull Message theMessage) throws MessagingException { ourLog.trace("Handling resource modified message: {}", theMessage); if (!(theMessage instanceof ResourceModifiedJsonMessage)) { @@ -109,7 +119,6 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { private void doMatchActiveSubscriptionsAndDeliver(ResourceModifiedMessage theMsg) { IIdType resourceId = theMsg.getId(myFhirContext); - Boolean isText = false; Collection subscriptions = mySubscriptionRegistry.getAll(); @@ -147,7 +156,6 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { EncodingEnum encoding = null; if (subscription.getPayloadString() != null && !subscription.getPayloadString().isEmpty()) { encoding = EncodingEnum.forContentType(subscription.getPayloadString()); - isText = subscription.getPayloadString().equals(Constants.CT_TEXT); } encoding = defaultIfNull(encoding, EncodingEnum.JSON); @@ -179,16 +187,16 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { } private boolean sendToDeliveryChannel(ActiveSubscription nextActiveSubscription, ResourceDeliveryMessage theDeliveryMsg) { - boolean retval = false; + boolean retVal = false; ResourceDeliveryJsonMessage wrappedMsg = new ResourceDeliveryJsonMessage(theDeliveryMsg); - MessageChannel deliveryChannel = mySubscriptionChannelRegistry.get(nextActiveSubscription.getChannelName()).getChannel(); + MessageChannel deliveryChannel = mySubscriptionChannelRegistry.getDeliverySenderChannel(nextActiveSubscription.getChannelName()); if (deliveryChannel != null) { - retval = true; + retVal = true; trySendToDeliveryChannel(wrappedMsg, deliveryChannel); } else { ourLog.warn("Do not have delivery channel for subscription {}", nextActiveSubscription.getId()); } - return retval; + return retVal; } private void trySendToDeliveryChannel(ResourceDeliveryJsonMessage theWrappedMsg, MessageChannel theDeliveryChannel) { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionRegisteringSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionRegisteringSubscriber.java new file mode 100644 index 00000000000..7684d0d760f --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionRegisteringSubscriber.java @@ -0,0 +1,94 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; + +import javax.annotation.Nonnull; + +/** + * Responsible for transitioning subscription resources from REQUESTED to ACTIVE + * Once activated, the subscription is added to the SubscriptionRegistry. + *

        + * Also validates criteria. If invalid, rejects the subscription without persisting the subscription. + */ +public class SubscriptionRegisteringSubscriber extends BaseSubscriberForSubscriptionResources implements MessageHandler { + private Logger ourLog = LoggerFactory.getLogger(SubscriptionRegisteringSubscriber.class); + @Autowired + private FhirContext myFhirContext; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + @Autowired + private SubscriptionCanonicalizer mySubscriptionCanonicalizer; + + /** + * Constructor + */ + public SubscriptionRegisteringSubscriber() { + super(); + } + + @Override + public void handleMessage(@Nonnull Message theMessage) throws MessagingException { + if (!(theMessage instanceof ResourceModifiedJsonMessage)) { + ourLog.warn("Received message of unexpected type on matching channel: {}", theMessage); + return; + } + + ResourceModifiedMessage payload = ((ResourceModifiedJsonMessage) theMessage).getPayload(); + + if (!isSubscription(payload)) { + return; + } + + switch (payload.getOperationType()) { + case DELETE: + mySubscriptionRegistry.unregisterSubscriptionIfRegistered(payload.getId(myFhirContext).getIdPart()); + break; + case CREATE: + case UPDATE: + IBaseResource subscription = payload.getNewPayload(myFhirContext); + String statusString = mySubscriptionCanonicalizer.getSubscriptionStatus(subscription); + if ("active".equals(statusString)) { + mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(payload.getNewPayload(myFhirContext)); + } else { + mySubscriptionRegistry.unregisterSubscriptionIfRegistered(payload.getId(myFhirContext).getIdPart()); + } + break; + case MANUALLY_TRIGGERED: + default: + break; + } + + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java index cdacb3401f5..cfab5cf8f8c 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.subscription.module.cache; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCache.java similarity index 97% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCache.java index 305a1889161..e9bbef87d90 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCache.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionCanonicalizer.java similarity index 97% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionCanonicalizer.java index a1229b456a5..2d482011869 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCanonicalizer.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionCanonicalizer.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L @@ -23,21 +23,23 @@ package ca.uhn.fhir.jpa.subscription.module.cache; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; import ca.uhn.fhir.model.dstu2.resource.Subscription; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import org.apache.commons.lang3.Validate; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r5.model.Coding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -49,7 +51,6 @@ import java.util.stream.Collectors; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; -@Service public class SubscriptionCanonicalizer { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionCanonicalizer.class); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java similarity index 96% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java index 4c3822cd420..c0d2b5860b1 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java similarity index 79% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java index 8a7d09abca2..ace9207a0a9 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L @@ -21,11 +21,14 @@ package ca.uhn.fhir.jpa.subscription.module.cache; */ import ca.uhn.fhir.jpa.api.IDaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.retry.Retrier; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionActivatingSubscriber; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; @@ -37,8 +40,6 @@ import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.HashSet; @@ -47,21 +48,26 @@ import java.util.Set; import java.util.concurrent.Semaphore; -@Service -@Lazy public class SubscriptionLoader { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionLoader.class); private static final int MAX_RETRIES = 60; // 60 * 5 seconds = 5 minutes private final Object mySyncSubscriptionsLock = new Object(); @Autowired - private ISubscriptionProvider mySubscriptionProvider; - @Autowired private SubscriptionRegistry mySubscriptionRegistry; @Autowired(required = false) - private IDaoRegistry myDaoRegistry; + private DaoRegistry myDaoRegistry; private Semaphore mySyncSubscriptionsSemaphore = new Semaphore(1); @Autowired private ISchedulerService mySchedulerService; + @Autowired + private SubscriptionActivatingSubscriber mySubscriptionActivatingInterceptor; + + /** + * Constructor + */ + public SubscriptionLoader() { + super(); + } /** * Read the existing subscriptions from the database @@ -80,28 +86,19 @@ public class SubscriptionLoader { } } - @VisibleForTesting - void acquireSemaphoreForUnitTest() throws InterruptedException { - mySyncSubscriptionsSemaphore.acquire(); - } - - @PostConstruct public void scheduleJob() { ScheduledJobDefinition jobDetail = new ScheduledJobDefinition(); jobDetail.setId(getClass().getName()); jobDetail.setJobClass(Job.class); mySchedulerService.scheduleLocalJob(DateUtils.MILLIS_PER_MINUTE, jobDetail); + + syncSubscriptions(); } - public static class Job implements HapiJob { - @Autowired - private SubscriptionLoader myTarget; - - @Override - public void execute(JobExecutionContext theContext) { - myTarget.syncSubscriptions(); - } + @VisibleForTesting + public void acquireSemaphoreForUnitTest() throws InterruptedException { + mySyncSubscriptionsSemaphore.acquire(); } @VisibleForTesting @@ -126,15 +123,12 @@ public class SubscriptionLoader { ourLog.debug("Starting sync subscriptions"); SearchParameterMap map = new SearchParameterMap(); map.add(Subscription.SP_STATUS, new TokenOrListParam() - // TODO KHS Ideally we should only be pulling ACTIVE subscriptions here, but this class is overloaded so that - // the @Scheduled task also activates requested subscriptions if their type was enabled after they were requested - // There should be a separate @Scheduled task that looks for requested subscriptions that need to be activated - // independent of the registry loading process. .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode())) .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()))); map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS); - IBundleProvider subscriptionBundleList = mySubscriptionProvider.search(map); + IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); + IBundleProvider subscriptionBundleList = subscriptionDao.search(map); Integer subscriptionCount = subscriptionBundleList.size(); assert subscriptionCount != null; @@ -145,26 +139,40 @@ public class SubscriptionLoader { List resourceList = subscriptionBundleList.getResources(0, subscriptionCount); Set allIds = new HashSet<>(); - int changesCount = 0; + int activatedCount = 0; + int registeredCount = 0; + for (IBaseResource resource : resourceList) { String nextId = resource.getIdElement().getIdPart(); allIds.add(nextId); - boolean changed = mySubscriptionProvider.loadSubscription(resource); - if (changed) { - changesCount++; + + boolean activated = mySubscriptionActivatingInterceptor.activateOrRegisterSubscriptionIfRequired(resource); + if (activated) { + activatedCount++; + } + + boolean registered = mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource); + if (registered) { + registeredCount++; } } mySubscriptionRegistry.unregisterAllSubscriptionsNotInCollection(allIds); - ourLog.debug("Finished sync subscriptions - found {}", resourceList.size()); + ourLog.debug("Finished sync subscriptions - activated {} and registered {}", resourceList.size(), registeredCount); - return changesCount; + return activatedCount; } } - @VisibleForTesting - public void setSubscriptionProviderForUnitTest(ISubscriptionProvider theSubscriptionProvider) { - mySubscriptionProvider = theSubscriptionProvider; + public static class Job implements HapiJob { + @Autowired + private SubscriptionLoader myTarget; + + @Override + public void execute(JobExecutionContext theContext) { + myTarget.syncSubscriptions(); + } } + } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java similarity index 85% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java index f507e980034..fd55e180981 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionRegistry.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; /*- * #%L @@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.subscription.module.cache; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscriptionDeliveryChannelNamer; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.ISubscriptionDeliveryChannelNamer; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -33,7 +33,6 @@ import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; import java.util.Collection; @@ -47,8 +46,6 @@ import java.util.Optional; * handlers are all caches in this registry so they can be removed it the subscription is deleted. */ -// TODO KHS Does jpa need a subscription registry if matching is disabled? -@Component public class SubscriptionRegistry { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRegistry.class); private final ActiveSubscriptionCache myActiveSubscriptionCache = new ActiveSubscriptionCache(); @@ -83,8 +80,7 @@ public class SubscriptionRegistry { return activeSubscription.map(ActiveSubscription::getSubscription); } - @SuppressWarnings("UnusedReturnValue") - private CanonicalSubscription registerSubscription(IIdType theId, IBaseResource theSubscription) { + private void registerSubscription(IIdType theId, IBaseResource theSubscription) { Validate.notNull(theId); String subscriptionId = theId.getIdPart(); Validate.notBlank(subscriptionId); @@ -104,10 +100,9 @@ public class SubscriptionRegistry { .add(CanonicalSubscription.class, canonicalized); myInterceptorBroadcaster.callHooks(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED, params); - return canonicalized; } - public void unregisterSubscription(String theSubscriptionId) { + public void unregisterSubscriptionIfRegistered(String theSubscriptionId) { Validate.notNull(theSubscriptionId); ourLog.info("Unregistering active subscription {}", theSubscriptionId); @@ -129,7 +124,7 @@ public class SubscriptionRegistry { List idsToDelete = myActiveSubscriptionCache.markAllSubscriptionsNotInCollectionForDeletionAndReturnIdsToDelete(theAllIds); for (String id : idsToDelete) { - unregisterSubscription(id); + unregisterSubscriptionIfRegistered(id); } } @@ -148,7 +143,7 @@ public class SubscriptionRegistry { updateSubscription(theSubscription); return true; } - unregisterSubscription(theSubscription.getIdElement().getIdPart()); + unregisterSubscriptionIfRegistered(theSubscription.getIdElement().getIdPart()); } if (Subscription.SubscriptionStatus.ACTIVE.equals(newSubscription.getStatus())) { registerSubscription(theSubscription.getIdElement(), theSubscription); @@ -177,15 +172,6 @@ public class SubscriptionRegistry { return theExistingSubscription.getChannelType().equals(theNewSubscription.getChannelType()); } - public boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) { - if (hasSubscription(theSubscription.getIdElement()).isPresent()) { - ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue()); - unregisterSubscription(theSubscription.getIdElement().getIdPart()); - return true; - } - return false; - } - public int size() { return myActiveSubscriptionCache.size(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseJsonMessage.java similarity index 68% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseJsonMessage.java index 346735af6ff..1bb28358726 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -20,15 +20,12 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; +import ca.uhn.fhir.model.api.IModelJson; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public abstract class BaseJsonMessage implements Message { +public abstract class BaseJsonMessage implements Message, IModelJson { private static final long serialVersionUID = 1L; @JsonProperty("headers") diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseResourceMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseResourceMessage.java similarity index 85% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseResourceMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseResourceMessage.java index 5e636b76575..ba753078243 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseResourceMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/BaseResourceMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; +import ca.uhn.fhir.model.api.IModelJson; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.Validate; @@ -30,9 +29,7 @@ import java.util.Map; import java.util.Optional; @SuppressWarnings("WeakerAccess") -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public abstract class BaseResourceMessage implements IResourceMessage { +public abstract class BaseResourceMessage implements IResourceMessage, IModelJson { @JsonProperty("attributes") private Map myAttributes; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscription.java similarity index 86% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscription.java index 0c359e82b8b..a8ba6d41f77 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscription.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -21,8 +21,7 @@ package ca.uhn.fhir.jpa.subscription.module; */ import ca.uhn.fhir.context.FhirContext; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; +import ca.uhn.fhir.model.api.IModelJson; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -39,9 +38,7 @@ import java.util.*; import static org.apache.commons.lang3.StringUtils.isNotBlank; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public class CanonicalSubscription implements Serializable, Cloneable { +public class CanonicalSubscription implements Serializable, Cloneable, IModelJson { private static final long serialVersionUID = 1L; @@ -279,9 +276,7 @@ public class CanonicalSubscription implements Serializable, Cloneable { } } - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) - public static class EmailDetails { + public static class EmailDetails implements IModelJson { @JsonProperty("from") private String myFrom; @@ -334,9 +329,7 @@ public class CanonicalSubscription implements Serializable, Cloneable { } } - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) - public static class RestHookDetails { + public static class RestHookDetails implements IModelJson { @JsonProperty("stripVersionId") private boolean myStripVersionId; @@ -391,9 +384,7 @@ public class CanonicalSubscription implements Serializable, Cloneable { } - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) - public static class CanonicalEventDefinition { + public static class CanonicalEventDefinition implements IModelJson { /** * Constructor diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscriptionChannelType.java similarity index 99% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscriptionChannelType.java index 474fd3ac1e3..6c523562f82 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/CanonicalSubscriptionChannelType.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/IResourceMessage.java similarity index 92% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/IResourceMessage.java index 6bc41c53e4d..0881b8021c5 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/IResourceMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryJsonMessage.java similarity index 75% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryJsonMessage.java index 783c19fca2d..81bd263ec23 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -20,13 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.ToStringBuilder; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) public class ResourceDeliveryJsonMessage extends BaseJsonMessage { @JsonProperty("payload") diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java similarity index 86% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java index b879301d43c..69b40d7004a 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -21,12 +21,8 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.rest.api.EncodingEnum; -import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.ToStringBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -35,8 +31,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import static org.apache.commons.lang3.StringUtils.isNotBlank; @SuppressWarnings("WeakerAccess") -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) public class ResourceDeliveryMessage extends BaseResourceMessage implements IResourceMessage { @JsonProperty("canonicalSubscription") diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedJsonMessage.java similarity index 72% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedJsonMessage.java index fea9e146a0a..f8b8407d686 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -20,14 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; * #L% */ -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.ToStringBuilder; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) public class ResourceModifiedJsonMessage extends BaseJsonMessage { @JsonProperty("payload") diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java similarity index 89% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java index b65eaa9b215..b72ad8ddc1a 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module; +package ca.uhn.fhir.jpa.subscription.model; /*- * #%L @@ -21,12 +21,9 @@ package ca.uhn.fhir.jpa.subscription.module; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseResourceMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceMessage; +import ca.uhn.fhir.model.api.IModelJson; import ca.uhn.fhir.util.ResourceReferenceInfo; -import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.builder.ToStringBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -37,9 +34,7 @@ import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public class ResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage { +public class ResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage, IModelJson { @JsonProperty("resourceId") private String myId; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java similarity index 56% rename from hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java index ecc3d1be305..4cb80cb599d 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.searchparam.config; +package ca.uhn.fhir.jpa.subscription.model.config; /*- * #%L - * HAPI FHIR Search Parameters + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,21 +20,25 @@ package ca.uhn.fhir.jpa.searchparam.config; * #L% */ -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; @Configuration -@EnableScheduling -@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam"}) -abstract public class BaseSearchParamConfig { +public class SubscriptionModelConfig { @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryImpl(); + public SubscriptionCanonicalizer subscriptionCanonicalizer(FhirContext theFhirContext) { + return new SubscriptionCanonicalizer(theFhirContext); } + + @Bean + public SubscriptionStrategyEvaluator subscriptionStrategyEvaluator() { + return new SubscriptionStrategyEvaluator(); + } + + } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscribableChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscribableChannel.java deleted file mode 100644 index ea53e3fd693..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscribableChannel.java +++ /dev/null @@ -1,103 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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.util.StopWatch; -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ExecutorSubscribableChannel; - -import java.util.ArrayList; -import java.util.concurrent.*; - -public class LinkedBlockingQueueSubscribableChannel implements SubscribableChannel { - private Logger ourLog = LoggerFactory.getLogger(LinkedBlockingQueueSubscribableChannel.class); - - private final ExecutorSubscribableChannel mySubscribableChannel; - private final BlockingQueue myQueue; - - public LinkedBlockingQueueSubscribableChannel(BlockingQueue theQueue, String theThreadNamingPattern, int theConcurrentConsumers) { - - ThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern(theThreadNamingPattern) - .daemon(false) - .priority(Thread.NORM_PRIORITY) - .build(); - RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> { - ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", theQueue.size()); - StopWatch sw = new StopWatch(); - try { - theQueue.put(theRunnable); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RejectedExecutionException("Task " + theRunnable.toString() + - " rejected from " + e.toString()); - } - ourLog.info("Slot become available after {}ms", sw.getMillis()); - }; - ThreadPoolExecutor executor = new ThreadPoolExecutor( - 1, - theConcurrentConsumers, - 0L, - TimeUnit.MILLISECONDS, - theQueue, - threadFactory, - rejectedExecutionHandler); - myQueue = theQueue; - mySubscribableChannel = new ExecutorSubscribableChannel(executor); - } - - @Override - public boolean subscribe(MessageHandler handler) { - return mySubscribableChannel.subscribe(handler); - } - - @Override - public boolean unsubscribe(MessageHandler handler) { - return mySubscribableChannel.unsubscribe(handler); - } - - @Override - public boolean send(Message message, long timeout) { - return mySubscribableChannel.send(message, timeout); - } - - @VisibleForTesting - public void clearInterceptorsForUnitTest() { - mySubscribableChannel.setInterceptors(new ArrayList<>()); - } - - @VisibleForTesting - public void addInterceptorForUnitTest(ChannelInterceptor theInterceptor) { - mySubscribableChannel.addInterceptor(theInterceptor); - } - - @VisibleForTesting - public int getQueueSizeForUnitTest() { - return myQueue.size(); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/LinkedBlockingQueueSubscribableChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/LinkedBlockingQueueSubscribableChannelFactory.java deleted file mode 100644 index 472a07a0931..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/LinkedBlockingQueueSubscribableChannelFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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.LinkedBlockingQueueSubscribableChannel; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscribableChannelFactory; -import org.springframework.messaging.SubscribableChannel; - -import java.util.concurrent.LinkedBlockingQueue; - -public class LinkedBlockingQueueSubscribableChannelFactory implements ISubscribableChannelFactory { - @Override - public SubscribableChannel createSubscribableChannel(String theChannelName, Class theMessageType, int theConcurrentConsumers) { - return new LinkedBlockingQueueSubscribableChannel(new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE), theChannelName + "-%d", theConcurrentConsumers); - } - - @Override - public int getDeliveryChannelConcurrentConsumers() { - return SubscriptionConstants.DELIVERY_CHANNEL_CONCURRENT_CONSUMERS; - } - - @Override - public int getMatchingChannelConcurrentConsumers() { - return SubscriptionConstants.MATCHING_CHANNEL_CONCURRENT_CONSUMERS; - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelFactory.java deleted file mode 100644 index c2a2eca6589..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.channel; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.SubscribableChannel; - -public class SubscriptionChannelFactory { - - @Autowired - private ISubscribableChannelFactory mySubscribableChannelFactory; - - public SubscribableChannel newDeliveryChannel(String theChannelName) { - return mySubscribableChannelFactory.createSubscribableChannel(theChannelName, ResourceDeliveryMessage.class, mySubscribableChannelFactory.getDeliveryChannelConcurrentConsumers()); - } - - public SubscribableChannel newMatchingChannel(String theChannelName) { - return mySubscribableChannelFactory.createSubscribableChannel(theChannelName, ResourceModifiedMessage.class, mySubscribableChannelFactory.getMatchingChannelConcurrentConsumers()); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java deleted file mode 100644 index 2a45b7a99f5..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.config; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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.interceptor.executor.InterceptorService; -import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribableChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscribableChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; - -@Configuration -@EnableScheduling -@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.subscription.module"}) -public abstract class BaseSubscriptionConfig { - @Bean - public ISubscribableChannelFactory subscribableChannelFactory() { - return new LinkedBlockingQueueSubscribableChannelFactory(); - } - - @Bean - public InterceptorService interceptorRegistry() { - return new InterceptorService("hapi-fhir-jpa-subscription"); - } - - @Bean - public SubscriptionChannelFactory subscriptionChannelFactory() { - return new SubscriptionChannelFactory(); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java deleted file mode 100644 index a0514cd8f17..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java +++ /dev/null @@ -1,38 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.config; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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.searchparam.config.SearchParamDstu3Config; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; - -@Import({SearchParamDstu3Config.class}) -public class SubscriptionDstu3Config extends BaseSubscriptionConfig { - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3") - public IValidationSupport validationSupportChainDstu3() { - return new DefaultProfileValidationSupport(); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java deleted file mode 100644 index d03ea805a68..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java +++ /dev/null @@ -1,39 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.config; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 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.searchparam.config.SearchParamR4Config; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; - -@Import({SearchParamR4Config.class}) -public class SubscriptionR4Config extends BaseSubscriptionConfig { - - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainR4") - public IValidationSupport validationSupportChainR4() { - return new DefaultProfileValidationSupport(); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java deleted file mode 100644 index 2a4f5ba24ab..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java +++ /dev/null @@ -1,50 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class FhirClientResourceRetriever implements IResourceRetriever { - private static final Logger ourLog = LoggerFactory.getLogger(FhirClientResourceRetriever.class); - - @Autowired - FhirContext myFhirContext; - @Autowired - IGenericClient myClient; - - @Override - public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException { - RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType()); - - return myClient.search().forResource(resourceDef.getName()).withIdAndCompartment(payloadId.getIdPart(), payloadId.getResourceType()).execute(); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java deleted file mode 100644 index 3d9b3aa64ed..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.server.SimpleBundleProvider; -import ca.uhn.fhir.util.BundleUtil; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class FhirClientSearchParamProvider implements ISearchParamProvider { - private static final Logger ourLog = LoggerFactory.getLogger(FhirClientSearchParamProvider.class); - - private IGenericClient myClient; - - @Autowired - public FhirClientSearchParamProvider(IGenericClient theClient) { - myClient = theClient; - } - - @Override - public IBundleProvider search(SearchParameterMap theParams) { - FhirContext fhirContext = myClient.getFhirContext(); - - IBaseBundle bundle = myClient - .search() - .forResource(ResourceTypeEnum.SEARCHPARAMETER.getCode()) - .cacheControl(new CacheControlDirective().setNoCache(true)) - .execute(); - - return new SimpleBundleProvider(BundleUtil.toListOfResources(fhirContext, bundle)); - } - - @Override - public int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval) { - return theSearchParamRegistry.doRefresh(theRefreshInterval); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java deleted file mode 100644 index 50847f4fe92..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.server.SimpleBundleProvider; -import ca.uhn.fhir.util.BundleUtil; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class FhirClientSubscriptionProvider implements ISubscriptionProvider { - @Autowired - private FhirContext myFhirContext; - @Autowired - private SubscriptionRegistry mySubscriptionRegistry; - - IGenericClient myClient; - - @Autowired - public FhirClientSubscriptionProvider(IGenericClient theClient) { - myClient = theClient; - } - - @Override - public IBundleProvider search(SearchParameterMap theMap) { - FhirContext fhirContext = myClient.getFhirContext(); - - String searchURL = ResourceTypeEnum.SUBSCRIPTION.getCode() + theMap.toNormalizedQueryString(myFhirContext); - - IBaseBundle bundle = myClient - .search() - .byUrl(searchURL) - .cacheControl(new CacheControlDirective().setNoCache(true)) - .execute(); - - return new SimpleBundleProvider(BundleUtil.toListOfResources(fhirContext, bundle)); - } - - @Override - public boolean loadSubscription(IBaseResource theResource) { - return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theResource); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java deleted file mode 100644 index 4a62d37baf1..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -/*- - * #%L - * HAPI FHIR Subscription Server - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -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.SubscriptionConstants; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; -import org.springframework.stereotype.Service; - -@Service -public class StandaloneSubscriptionMessageHandler implements MessageHandler { - private static final Logger ourLog = LoggerFactory.getLogger(StandaloneSubscriptionMessageHandler.class); - - @Autowired - FhirContext myFhirContext; - @Autowired - SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; - @Autowired - SubscriptionRegistry mySubscriptionRegistry; - @Autowired - SubscriptionCanonicalizer mySubscriptionCanonicalizer; - - @Override - public void handleMessage(Message theMessage) throws MessagingException { - if (!(theMessage instanceof ResourceModifiedJsonMessage)) { - ourLog.warn("Unexpected message payload type: {}", theMessage); - return; - } - updateSubscriptionRegistryAndPerformMatching(((ResourceModifiedJsonMessage) theMessage).getPayload()); - } - - public void updateSubscriptionRegistryAndPerformMatching(ResourceModifiedMessage theResourceModifiedMessage) { - switch (theResourceModifiedMessage.getOperationType()) { - case DELETE: - if (isSubscription(theResourceModifiedMessage)) { - mySubscriptionRegistry.unregisterSubscription(theResourceModifiedMessage.getId(myFhirContext).getIdPart()); - } - return; - case CREATE: - case UPDATE: - if (isSubscription(theResourceModifiedMessage)) { - registerActiveSubscription(theResourceModifiedMessage.getNewPayload(myFhirContext)); - } - break; - default: - break; - } - - mySubscriptionMatchingSubscriber.matchActiveSubscriptionsAndDeliver(theResourceModifiedMessage); - } - - private boolean isSubscription(ResourceModifiedMessage theResourceModifiedMessage) { - String resourceType; - IIdType id = theResourceModifiedMessage.getId(myFhirContext); - if (id != null) { - resourceType = id.getResourceType(); - } else { - resourceType = theResourceModifiedMessage.getNewPayload(myFhirContext).getIdElement().getResourceType(); - } - if (resourceType == null) { - return false; - } - RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(resourceType); - return resourceDef.getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode()); - } - - private void registerActiveSubscription(IBaseResource theSubscription) { - String status = mySubscriptionCanonicalizer.getSubscriptionStatus(theSubscription); - if (SubscriptionConstants.ACTIVE_STATUS.equals(status)) { - mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription); - } - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/config/SubscriptionSubmitterConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/config/SubscriptionSubmitterConfig.java new file mode 100644 index 00000000000..b0eb39b0213 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/config/SubscriptionSubmitterConfig.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.jpa.subscription.submit.config; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.model.config.SubscriptionModelConfig; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionSubmitInterceptorLoader; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionValidatingInterceptor; +import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; +import ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; + +/** + * This Spring config should be imported by a system that submits resources to the + * matching queue for processing + */ +@Configuration +@Import(SubscriptionModelConfig.class) +public class SubscriptionSubmitterConfig { + + @Bean + public SubscriptionMatcherInterceptor subscriptionMatcherInterceptor() { + return new SubscriptionMatcherInterceptor(); + } + + @Bean + public SubscriptionValidatingInterceptor subscriptionValidatingInterceptor() { + return new SubscriptionValidatingInterceptor(); + } + + @Bean + public SubscriptionSubmitInterceptorLoader subscriptionMatcherInterceptorLoader() { + return new SubscriptionSubmitInterceptorLoader(); + } + + @Bean + @Lazy + public ISubscriptionTriggeringSvc subscriptionTriggeringSvc() { + return new SubscriptionTriggeringSvcImpl(); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java similarity index 68% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java index 7f20e410e30..d8136070cb4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java @@ -1,12 +1,17 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.submit.interceptor; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.interceptor.api.*; -import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.IResourceModifiedConsumer; +import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.rest.api.server.RequestDetails; import com.google.common.annotations.VisibleForTesting; @@ -15,17 +20,15 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.stereotype.Component; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.MessageChannel; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; -import javax.annotation.PreDestroy; - /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -43,21 +46,17 @@ import javax.annotation.PreDestroy; * #L% */ -@Component -@Lazy -@Interceptor() +@Interceptor public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer { - public static final String SUBSCRIPTION_MATCHING_CHANNEL_NAME = "subscription-matching"; - protected SubscribableChannel myMatchingChannel; - @Autowired - protected SubscriptionChannelFactory mySubscriptionChannelFactory; private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class); @Autowired private FhirContext myFhirContext; @Autowired - private SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; - @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private SubscriptionChannelFactory mySubscriptionChannelFactory; + + private volatile MessageChannel myMatchingChannel; /** * Constructor @@ -66,41 +65,38 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer super(); } - public void start() { + @EventListener(classes = {ContextRefreshedEvent.class}) + public void startIfNeeded() { if (myMatchingChannel == null) { - myMatchingChannel = mySubscriptionChannelFactory.newMatchingChannel(SUBSCRIPTION_MATCHING_CHANNEL_NAME); - } - myMatchingChannel.subscribe(mySubscriptionMatchingSubscriber); - ourLog.info("Subscription Matching Subscriber subscribed to Matching Channel {} with name {}", myMatchingChannel.getClass().getName(), SUBSCRIPTION_MATCHING_CHANNEL_NAME); - - } - - @SuppressWarnings("unused") - @PreDestroy - public void preDestroy() { - - if (myMatchingChannel != null) { - myMatchingChannel.unsubscribe(mySubscriptionMatchingSubscriber); + myMatchingChannel = mySubscriptionChannelFactory.newMatchingSendingChannel(SubscriptionMatchingSubscriber.SUBSCRIPTION_MATCHING_CHANNEL_NAME, null); } } @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED) public void resourceCreated(IBaseResource theResource, RequestDetails theRequest) { + startIfNeeded(); submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE, theRequest); } @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED) public void resourceDeleted(IBaseResource theResource, RequestDetails theRequest) { + startIfNeeded(); submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE, theRequest); } @Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED) public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource, RequestDetails theRequest) { + startIfNeeded(); submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE, theRequest); } - private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType, RequestDetails theRequest) { + /** + * This is an internal API - Use with caution! + */ + @Override + public void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType, RequestDetails theRequest) { ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType); + // Interceptor call: SUBSCRIPTION_RESOURCE_MODIFIED HookParams params = new HookParams() .add(ResourceModifiedMessage.class, msg); @@ -112,16 +108,6 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer submitResourceModified(msg); } - protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) { - ourLog.trace("Sending resource modified message to processing channel"); - Validate.notNull(myMatchingChannel, "A SubscriptionMatcherInterceptor has been registered without calling start() on it."); - myMatchingChannel.send(new ResourceModifiedJsonMessage(theMessage)); - } - - public void setFhirContext(FhirContext theCtx) { - myFhirContext = theCtx; - } - /** * This is an internal API - Use with caution! */ @@ -149,8 +135,18 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer } } + protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) { + ourLog.trace("Sending resource modified message to processing channel"); + Validate.notNull(myMatchingChannel, "A SubscriptionMatcherInterceptor has been registered without calling start() on it."); + myMatchingChannel.send(new ResourceModifiedJsonMessage(theMessage)); + } + + public void setFhirContext(FhirContext theCtx) { + myFhirContext = theCtx; + } + @VisibleForTesting - LinkedBlockingQueueSubscribableChannel getProcessingChannelForUnitTest() { - return (LinkedBlockingQueueSubscribableChannel) myMatchingChannel; + public LinkedBlockingChannel getProcessingChannelForUnitTest() { + return (LinkedBlockingChannel) myMatchingChannel; } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoader.java new file mode 100644 index 00000000000..72814d0e088 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoader.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jpa.subscription.submit.interceptor; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.dstu2.model.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +import javax.annotation.PostConstruct; +import java.util.Set; + +public class SubscriptionSubmitInterceptorLoader { + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionSubmitInterceptorLoader.class); + + @Autowired + private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; + @Autowired + private SubscriptionValidatingInterceptor mySubscriptionValidatingInterceptor; + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private ApplicationContext myApplicationContext; + @Autowired + private IInterceptorService myInterceptorRegistry; + + @PostConstruct + public void start() { + Set supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes(); + + if (supportedSubscriptionTypes.isEmpty()) { + ourLog.info("Subscriptions are disabled on this server. Subscriptions will not be activated and incoming resources will not be matched against subscriptions."); + } else { + ourLog.info("Registering subscription matcher interceptor"); + myInterceptorRegistry.registerInterceptor(mySubscriptionMatcherInterceptor); + } + + myInterceptorRegistry.registerInterceptor(mySubscriptionValidatingInterceptor); + } + + @VisibleForTesting + public void unregisterInterceptorsForUnitTest() { + myInterceptorRegistry.unregisterInterceptor(mySubscriptionMatcherInterceptor); + myInterceptorRegistry.unregisterInterceptor(mySubscriptionValidatingInterceptor); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java new file mode 100644 index 00000000000..b140507ff5e --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java @@ -0,0 +1,174 @@ +package ca.uhn.fhir.jpa.subscription.submit.interceptor; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +@Interceptor +public class SubscriptionValidatingInterceptor { + + @Autowired + private SubscriptionCanonicalizer mySubscriptionCanonicalizer; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; + @Autowired + private FhirContext myFhirContext; + + @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED) + public void resourcePreCreate(IBaseResource theResource) { + validateSubmittedSubscription(theResource); + } + + @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED) + public void resourcePreCreate(IBaseResource theOldResource, IBaseResource theResource) { + validateSubmittedSubscription(theResource); + } + + @VisibleForTesting + public void setFhirContextForUnitTest(FhirContext theFhirContext) { + myFhirContext = theFhirContext; + } + + public void validateSubmittedSubscription(IBaseResource theSubscription) { + if (!"Subscription".equals(myFhirContext.getResourceDefinition(theSubscription).getName())) { + return; + } + + CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription); + boolean finished = false; + if (subscription.getStatus() == null) { + throw new UnprocessableEntityException("Can not process submitted Subscription - Subscription.status must be populated on this server"); + } + + switch (subscription.getStatus()) { + case REQUESTED: + case ACTIVE: + break; + case ERROR: + case OFF: + case NULL: + finished = true; + break; + } + + mySubscriptionCanonicalizer.setMatchingStrategyTag(theSubscription, null); + + if (!finished) { + + String query = subscription.getCriteriaString(); + if (isBlank(query)) { + throw new UnprocessableEntityException("Subscription.criteria must be populated"); + } + + int sep = query.indexOf('?'); + if (sep <= 1) { + throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\""); + } + + String resType = query.substring(0, sep); + if (resType.contains("/")) { + throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\""); + } + + validateChannelType(subscription); + + if (!myDaoRegistry.isResourceTypeSupported(resType)) { + throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resType); + } + + try { + SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(query); + mySubscriptionCanonicalizer.setMatchingStrategyTag(theSubscription, strategy); + } catch (InvalidRequestException | DataFormatException e) { + throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + query + " " + e.getMessage()); + } + + if (subscription.getChannelType() == null) { + throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server"); + } + + } + } + + @SuppressWarnings("WeakerAccess") + protected void validateChannelType(CanonicalSubscription theSubscription) { + if (theSubscription.getChannelType() == null) { + throw new UnprocessableEntityException("Subscription.channel.type must be populated"); + } else if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) { + validateChannelPayload(theSubscription); + validateChannelEndpoint(theSubscription); + } + } + + @SuppressWarnings("WeakerAccess") + protected void validateChannelEndpoint(CanonicalSubscription theResource) { + if (isBlank(theResource.getEndpointUrl())) { + throw new UnprocessableEntityException("Rest-hook subscriptions must have Subscription.channel.endpoint defined"); + } + } + + @SuppressWarnings("WeakerAccess") + protected void validateChannelPayload(CanonicalSubscription theResource) { + if (!isBlank(theResource.getPayloadString()) && EncodingEnum.forContentType(theResource.getPayloadString()) == null) { + throw new UnprocessableEntityException("Invalid value for Subscription.channel.payload: " + theResource.getPayloadString()); + } + } + + @SuppressWarnings("WeakerAccess") + @VisibleForTesting + public void setSubscriptionCanonicalizerForUnitTest(SubscriptionCanonicalizer theSubscriptionCanonicalizer) { + mySubscriptionCanonicalizer = theSubscriptionCanonicalizer; + } + + @SuppressWarnings("WeakerAccess") + @VisibleForTesting + public void setDaoRegistryForUnitTest(DaoRegistry theDaoRegistry) { + myDaoRegistry = theDaoRegistry; + } + + + @VisibleForTesting + @SuppressWarnings("WeakerAccess") + public void setSubscriptionStrategyEvaluatorForUnitTest(SubscriptionStrategyEvaluator theSubscriptionStrategyEvaluator) { + mySubscriptionStrategyEvaluator = theSubscriptionStrategyEvaluator; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionTriggeringSvc.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/ISubscriptionTriggeringSvc.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionTriggeringSvc.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/ISubscriptionTriggeringSvc.java index 3fdbfd869a0..9a457dc6829 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionTriggeringSvc.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/ISubscriptionTriggeringSvc.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.triggering; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java index 49506449095..ee7cbbd9820 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.triggering; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR Subscription Server * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -22,18 +22,18 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; 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.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; -import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.IResourceModifiedConsumer; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.api.CacheControlDirective; @@ -59,7 +59,6 @@ import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -75,13 +74,12 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider.RESOURCE_ID; +import static ca.uhn.fhir.jpa.model.util.ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -@Service public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc { - private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class); + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringSvcImpl.class); private static final int DEFAULT_MAX_SUBMIT = 10000; private final List myActiveJobs = new ArrayList<>(); @Autowired @@ -129,8 +127,8 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc // Resource URLs must be compete for (IPrimitiveType next : resourceIds) { IdType resourceId = new IdType(next.getValue()); - ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasResourceType(), RESOURCE_ID + " parameter must have resource type"); - ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasIdPart(), RESOURCE_ID + " parameter must have resource ID part"); + ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasResourceType(), SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID + " parameter must have resource type"); + ValidateUtil.isTrueOrThrowInvalidRequest(resourceId.hasIdPart(), SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID + " parameter must have resource ID part"); } // Search URLs must be valid diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.java similarity index 94% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.java index 4baef540493..288ad03b2d5 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.interceptor; +package ca.uhn.fhir.jpa.subscription.util; /*- * #%L @@ -24,9 +24,9 @@ import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import ca.uhn.fhir.util.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +43,7 @@ import java.util.function.Function; * This interceptor loges each step in the processing pipeline with a * different event code, using the event codes itemized in * {@link EventCodeEnum}. By default these are each placed in a logger with - * a different name (e.g. ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor.SUBS20 + * a different name (e.g. ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor.SUBS20 * in order to facilitate fine-grained logging controls where some codes are omitted and * some are not. *

        @@ -63,7 +63,7 @@ public class SubscriptionDebugLogInterceptor { private final EnumMap myLoggers; /** - * Constructor that logs at INFO level to the logger ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor + * Constructor that logs at INFO level to the logger ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor */ public SubscriptionDebugLogInterceptor() { this(defaultLogFactory(), Level.INFO); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java new file mode 100644 index 00000000000..51d300fb3bb --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java @@ -0,0 +1,75 @@ +package ca.uhn.fhir.jpa.subscription.channel.subscription; + +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.GenericMessage; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SubscriptionChannelFactoryTest { + + private SubscriptionChannelFactory mySvc; + + @Mock + private ChannelInterceptor myInterceptor; + @Captor + private ArgumentCaptor myExceptionCaptor; + + @Before + public void before() { + mySvc = new SubscriptionChannelFactory(new LinkedBlockingChannelFactory()); + } + + /** + * Make sure the channel doesn't silently swallow exceptions + */ + @Test + public void testInterceptorsOnChannelWrapperArePropagated() { + + IChannelReceiver channel = mySvc.newDeliveryReceivingChannel("CHANNEL_NAME", null); + channel.subscribe(new NpeThrowingHandler()); + channel.addInterceptor(myInterceptor); + + Message input = new GenericMessage<>("TEST"); + + when(myInterceptor.preSend(any(),any())).thenAnswer(t->t.getArgument(0, Message.class)); + + try { + channel.send(input); + fail(); + } catch (MessageDeliveryException e) { + assertTrue(e.getCause() instanceof NullPointerException); + } + + verify(myInterceptor, times(1)).afterSendCompletion(any(), any(), anyBoolean(), myExceptionCaptor.capture()); + + assertTrue(myExceptionCaptor.getValue() instanceof NullPointerException); + } + + + private class NpeThrowingHandler implements MessageHandler { + @Override + public void handleMessage(Message message) throws MessagingException { + throw new NullPointerException("THIS IS THE MESSAGE"); + } + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriberTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriberTest.java similarity index 92% rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriberTest.java rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriberTest.java index b0e0f9930bf..b4dc18f037d 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriberTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/deliver/BaseSubscriptionDeliverySubscriberTest.java @@ -1,11 +1,14 @@ -package ca.uhn.fhir.jpa.subscription.module.subscriber; +package ca.uhn.fhir.jpa.subscription.match.deliver; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; @@ -18,7 +21,6 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.GenericMessage; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcherTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcherTest.java new file mode 100644 index 00000000000..63cfd62b281 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/DaoSubscriptionMatcherTest.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.jpa.subscription.match.matcher.matching; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.PlatformTransactionManager; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { + SubscriptionProcessorConfig.class, + SearchParamConfig.class, + DaoSubscriptionMatcherTest.MyConfig.class +}) +public class DaoSubscriptionMatcherTest { + + @Autowired(required = false) + private PlatformTransactionManager myTxManager; + @Autowired + private DaoSubscriptionMatcher mySvc; + @MockBean + private ModelConfig myModelConfig; + @MockBean + private DaoConfig myDaoConfig; + @MockBean + private ISearchParamProvider mySearchParamProvider; + @MockBean + private ISchedulerService mySchedulerService; + @MockBean + private IInterceptorService myInterceptorService; + @MockBean + private DaoRegistry myDaoRegistry; + @MockBean + private IValidationSupport myValidationSupport; + @MockBean + private SubscriptionChannelFactory mySubscriptionChannelFactory; + + /** + * Make sure that if we're only running the {@link SubscriptionSubmitterConfig}, we don't need + * a transaction manager + */ + @Test + public void testSubmitterCanRunWithoutTransactionManager() { + assertNull(myTxManager); + } + + @Configuration + public static class MyConfig { + + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } + + @Bean + public FhirContext fhirContext() { + return FhirContext.forR4(); + } + + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCacheTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCacheTest.java similarity index 91% rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCacheTest.java rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCacheTest.java index 52fe9fab7c1..7dd69d83bfa 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCacheTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscriptionCacheTest.java @@ -1,6 +1,6 @@ -package ca.uhn.fhir.jpa.subscription.module.cache; +package ca.uhn.fhir.jpa.subscription.match.registry; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import ca.uhn.fhir.model.primitive.IdDt; import org.junit.Test; @@ -8,7 +8,11 @@ import java.util.ArrayList; import java.util.List; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class ActiveSubscriptionCacheTest { static final String ID1 = "id1"; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java index f74460fa338..01ba5f934d2 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.subscription.module; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.jpa.subscription.module.config.TestSubscriptionDstu3Config; import ca.uhn.fhir.util.StopWatch; import org.hl7.fhir.dstu3.model.Subscription; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java index b0730ecff71..c7b0369d2c6 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java @@ -1,21 +1,35 @@ package ca.uhn.fhir.jpa.subscription.module; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.interceptor.executor.InterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider; -import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider; +import ca.uhn.fhir.jpa.subscription.module.config.TestSubscriptionConfig; import ca.uhn.fhir.rest.api.server.IBundleProvider; import org.junit.After; import org.junit.runner.RunWith; 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 org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + SearchParamConfig.class, + SubscriptionProcessorConfig.class, + TestSubscriptionConfig.class, + BaseSubscriptionTest.MyConfig.class +}) public abstract class BaseSubscriptionTest { @Autowired - MockFhirClientSubscriptionProvider myMockFhirClientSubscriptionProvider; + protected IInterceptorService myInterceptorRegistry; @Autowired ISearchParamRegistry mySearchParamRegistry; @@ -23,10 +37,6 @@ public abstract class BaseSubscriptionTest { @Autowired MockFhirClientSearchParamProvider myMockFhirClientSearchParamProvider; - @Autowired - protected - IInterceptorService myInterceptorRegistry; - @After public void afterClearAnonymousLambdas() { myInterceptorRegistry.unregisterAllInterceptors(); @@ -36,4 +46,25 @@ public abstract class BaseSubscriptionTest { myMockFhirClientSearchParamProvider.setBundleProvider(theBundleProvider); mySearchParamRegistry.forceRefresh(); } + + @Configuration + public static class MyConfig { + + @Bean + public DaoConfig daoConfig() { + return new DaoConfig(); + } + + @Bean + public SubscriptionChannelFactory mySubscriptionChannelFactory() { + return new SubscriptionChannelFactory(new LinkedBlockingChannelFactory()); + } + + @Bean + public IInterceptorService interceptorService() { + return new InterceptorService(); + } + + + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionTest.java index a67ede950c6..b0c9751cff6 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionTest.java @@ -1,9 +1,10 @@ package ca.uhn.fhir.jpa.subscription.module; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryJsonMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import com.fasterxml.jackson.databind.ObjectMapper; import org.assertj.core.util.Lists; import org.hamcrest.Matchers; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java index 36969daf1c7..0d3534384eb 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.subscription.module; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import org.hl7.fhir.r4.model.Organization; import org.junit.Test; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java new file mode 100644 index 00000000000..9ea109593bf --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java @@ -0,0 +1,62 @@ +package ca.uhn.fhir.jpa.subscription.module; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@Import({SearchParamConfig.class}) +@EnableScheduling +public class SubscriptionTestConfig { + + @Autowired + private FhirContext myFhirContext; + + @Primary + @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChain") + public IValidationSupport validationSupportChainR4() { + return myFhirContext.getValidationSupport(); + } + + @Bean + public IChannelFactory subscribableChannelFactory() { + return new LinkedBlockingChannelFactory(); + } + + @Bean + public SubscriptionChannelFactory subscriptionChannelFactory(IChannelFactory theQueueChannelFactory) { + return new SubscriptionChannelFactory(theQueueChannelFactory); + } + + +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java old mode 100755 new mode 100644 index f7e3e55ad72..845dadbc40e --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java @@ -1,53 +1,13 @@ package ca.uhn.fhir.jpa.subscription.module.cache; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; -import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; import ca.uhn.fhir.jpa.subscription.module.standalone.BaseBlockingQueueSubscribableChannelDstu3Test; -import org.hl7.fhir.dstu3.model.Subscription; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; - public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannelDstu3Test { - private static final int MOCK_FHIR_CLIENT_FAILURES = 3; - @Autowired - private MockFhirClientSubscriptionProvider myMockFhirClientSubscriptionProvider; - - @Before - public void setFailCount() { - myMockFhirClientSubscriptionProvider.setFailCount(MOCK_FHIR_CLIENT_FAILURES); - } - - @After - public void restoreFailCount() { - myMockFhirClientSubscriptionProvider.setFailCount(0); - } - - @Test - public void testSubscriptionLoaderFhirClientDown() throws Exception { - String payload = "application/fhir+json"; - - String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; - String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; - - List subs = new ArrayList<>(); - subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase)); - subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase)); - - mySubscriptionActivatedPost.setExpectedCount(2); - initSubscriptionLoader(subs, "uuid"); - mySubscriptionActivatedPost.awaitExpected(); - assertEquals(0, myMockFhirClientSubscriptionProvider.getFailCount()); - } - @Test public void testMultipleThreadsDontBlock() throws InterruptedException { diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistrySharedTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistrySharedTest.java index 0c5924b0651..884f56810e3 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistrySharedTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistrySharedTest.java @@ -1,21 +1,26 @@ package ca.uhn.fhir.jpa.subscription.module.cache; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscriptionDeliveryChannelNamer; +import ca.uhn.fhir.jpa.subscription.channel.subscription.ISubscriptionDeliveryChannelNamer; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import org.hl7.fhir.dstu3.model.Subscription; import org.junit.Test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@ContextConfiguration(classes = { + SubscriptionRegistrySharedTest.SpringConfig.class +}) public class SubscriptionRegistrySharedTest extends BaseSubscriptionRegistryTest { private static final String OTHER_ID = "OTHER_ID"; @Configuration public static class SpringConfig { + @Primary @Bean ISubscriptionDeliveryChannelNamer subscriptionDeliveryChannelNamer() { @@ -28,6 +33,7 @@ public class SubscriptionRegistrySharedTest extends BaseSubscriptionRegistryTest return "shared"; } } + } @Test diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java index 12c37ff025e..0cf1edb523f 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.subscription.module.cache; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; import org.hl7.fhir.dstu3.model.Subscription; import org.junit.Test; @@ -52,7 +53,7 @@ public class SubscriptionRegistryTest extends BaseSubscriptionRegistryTest { assertRegistrySize(0); mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(subscription); assertRegistrySize(1); - mySubscriptionRegistry.unregisterSubscription(subscription.getId()); + mySubscriptionRegistry.unregisterSubscriptionIfRegistered(subscription.getId()); assertRegistrySize(0); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java index 3871d77ef3f..57c4e2ab888 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java @@ -1,8 +1,12 @@ package ca.uhn.fhir.jpa.subscription.module.channel; import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.model.primitive.IdDt; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,6 +18,8 @@ import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(SpringRunner.class) @@ -39,8 +45,6 @@ public class SubscriptionChannelRegistryTest { @Test public void testAddAddRemoveRemove() { - when(myModelConfig.isSubscriptionMatchingEnabled()).thenReturn(true); - CanonicalSubscription cansubA = new CanonicalSubscription(); cansubA.setIdElement(new IdDt("A")); ActiveSubscription activeSubscriptionA = new ActiveSubscription(cansubA, TEST_CHANNEL_NAME); @@ -48,13 +52,15 @@ public class SubscriptionChannelRegistryTest { cansubB.setIdElement(new IdDt("B")); ActiveSubscription activeSubscriptionB = new ActiveSubscription(cansubB, TEST_CHANNEL_NAME); - assertNull(mySubscriptionChannelRegistry.get(TEST_CHANNEL_NAME)); + when(mySubscriptionDeliveryChannelFactory.newDeliverySendingChannel(any(), any())).thenReturn(mock(IChannelProducer.class)); + + assertNull(mySubscriptionChannelRegistry.getDeliveryReceiverChannel(TEST_CHANNEL_NAME)); mySubscriptionChannelRegistry.add(activeSubscriptionA); - assertNotNull(mySubscriptionChannelRegistry.get(TEST_CHANNEL_NAME)); + assertNotNull(mySubscriptionChannelRegistry.getDeliveryReceiverChannel(TEST_CHANNEL_NAME)); mySubscriptionChannelRegistry.add(activeSubscriptionB); mySubscriptionChannelRegistry.remove(activeSubscriptionB); - assertNotNull(mySubscriptionChannelRegistry.get(TEST_CHANNEL_NAME)); + assertNotNull(mySubscriptionChannelRegistry.getDeliveryReceiverChannel(TEST_CHANNEL_NAME)); mySubscriptionChannelRegistry.remove(activeSubscriptionA); - assertNull(mySubscriptionChannelRegistry.get(TEST_CHANNEL_NAME)); + assertNull(mySubscriptionChannelRegistry.getDeliveryReceiverChannel(TEST_CHANNEL_NAME)); } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java index 14e0f8834d6..3092edec296 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java @@ -1,20 +1,33 @@ package ca.uhn.fhir.jpa.subscription.module.config; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import org.springframework.beans.factory.annotation.Autowired; -public class MockFhirClientSearchParamProvider extends FhirClientSearchParamProvider { +public class MockFhirClientSearchParamProvider implements ISearchParamProvider { private final MockProvider myMockProvider = new MockProvider(); + @Autowired + private SearchParamRegistryImpl mySearchParamRegistry; + public MockFhirClientSearchParamProvider() { - super(null); + super(); } public void setBundleProvider(IBundleProvider theBundleProvider) { myMockProvider.setBundleProvider(theBundleProvider); } + public void setFailCount(int theFailCount) { myMockProvider.setFailCount(theFailCount); } + public int getFailCount() { return myMockProvider.getFailCount(); } @Override public IBundleProvider search(SearchParameterMap theParams) { return myMockProvider.search(theParams); } + + @Override + public int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval) { + mySearchParamRegistry.doRefresh(0); + return 0; + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java deleted file mode 100644 index fba2470251d..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.config; - -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider; -import ca.uhn.fhir.rest.api.server.IBundleProvider; - -public class MockFhirClientSubscriptionProvider extends FhirClientSubscriptionProvider { - private final MockProvider myMockProvider = new MockProvider(); - - public MockFhirClientSubscriptionProvider() { - super(null); - } - - public void setBundleProvider(IBundleProvider theBundleProvider) { myMockProvider.setBundleProvider(theBundleProvider); } - public void setFailCount(int theFailCount) { myMockProvider.setFailCount(theFailCount); } - public int getFailCount() { return myMockProvider.getFailCount(); } - - @Override - public IBundleProvider search(SearchParameterMap theParams) { return myMockProvider.search(theParams); } -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java index 2d3ff058918..0a3e7b9f6f5 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java @@ -1,15 +1,13 @@ package ca.uhn.fhir.jpa.subscription.module.config; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.InMemorySubscriptionMatcher; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.test.context.TestPropertySource; @Configuration @@ -18,6 +16,11 @@ import org.springframework.test.context.TestPropertySource; }) public class TestSubscriptionConfig { + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } + @Bean public ModelConfig modelConfig() { return new ModelConfig(); @@ -29,7 +32,7 @@ public class TestSubscriptionConfig { }; @Bean - public ISubscriptionMatcher inMemorySubscriptionMatcher() { + public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() { return new InMemorySubscriptionMatcher(); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java index bd833138922..2bb91bdd147 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java @@ -1,43 +1,45 @@ package ca.uhn.fhir.jpa.subscription.module.config; -import ca.uhn.fhir.jpa.api.IDaoRegistry; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @Configuration @Import(TestSubscriptionConfig.class) -public class TestSubscriptionDstu3Config extends SubscriptionDstu3Config { +public class TestSubscriptionDstu3Config { + + @Bean + public FhirContext fhirContext() { + return FhirContext.forDstu3(); + } + + @Bean + public IValidationSupport validationSupport() { + return FhirContext.forDstu3().getValidationSupport(); + } + @Bean @Primary public ISearchParamProvider searchParamProvider() { return new MockFhirClientSearchParamProvider(); } - @Bean - @Primary - public ISubscriptionProvider subscriptionProvider() { - return new MockFhirClientSubscriptionProvider(); - } - - @Bean - public IDaoRegistry daoRegistry() { - IDaoRegistry retVal = mock(IDaoRegistry.class); - when(retVal.isResourceTypeSupported(any())).thenReturn(true); - return retVal; - } - @Bean public ISchedulerService schedulerService() { return mock(ISchedulerService.class); } + @Bean + public DaoRegistry daoRegistry() { + return new DaoRegistry(); + } + } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java index 82c9384d4de..dc2b5738e7c 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java @@ -4,6 +4,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; @@ -20,7 +22,9 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class InMemorySubscriptionMatcherR3Test extends BaseSubscriptionDstu3Test { @Autowired diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluatorTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluatorTest.java index 1dbbc3159f7..6d01c515ac3 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluatorTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluatorTest.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.junit.Rule; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java index 02572f42786..c1f6a7de1a8 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java @@ -4,18 +4,16 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.test.concurrency.IPointcutLatch; -import ca.uhn.test.concurrency.PointcutLatch; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.channel.subscription.ISubscriptionDeliveryChannelNamer; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.channel.ISubscriptionDeliveryChannelNamer; -import ca.uhn.fhir.jpa.subscription.module.channel.SubscriptionChannelFactory; -import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriberTest; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.Create; @@ -25,13 +23,18 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.test.concurrency.IPointcutLatch; +import ca.uhn.test.concurrency.PointcutLatch; import com.google.common.collect.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.After; import org.junit.AfterClass; @@ -40,6 +43,8 @@ import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.messaging.MessageHandler; import org.springframework.messaging.SubscribableChannel; import javax.servlet.http.HttpServletRequest; @@ -49,12 +54,24 @@ import java.util.List; public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends BaseSubscriptionDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionMatchingSubscriberTest.class); + public static final ChannelConsumerSettings CONSUMER_OPTIONS = new ChannelConsumerSettings().setConcurrentConsumers(1); protected static ObservationListener ourObservationListener; @Autowired FhirContext myFhirContext; + + // Caused by: java.lang.IllegalStateException: Unable to register mock bean org.springframework.messaging.MessageHandler expected a single matching bean to replace but found [subscriptionActivatingSubscriber, subscriptionDeliveringEmailSubscriber, subscriptionDeliveringRestHookSubscriber, subscriptionMatchingSubscriber, subscriptionRegisteringSubscriber] + @Autowired - StandaloneSubscriptionMessageHandler myStandaloneSubscriptionMessageHandler; + @Qualifier("subscriptionActivatingSubscriber") + MessageHandler mySubscriptionActivatingSubscriber; + @Autowired + @Qualifier("subscriptionRegisteringSubscriber") + MessageHandler subscriptionRegisteringSubscriber; + @Autowired + @Qualifier("subscriptionMatchingSubscriber") + MessageHandler subscriptionMatchingSubscriber; + @Autowired SubscriptionChannelFactory mySubscriptionChannelFactory; @Autowired @@ -62,8 +79,6 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base @Autowired protected SubscriptionRegistry mySubscriptionRegistry; @Autowired - private MockFhirClientSubscriptionProvider myMockFhirClientSubscriptionProvider; - @Autowired private SubscriptionLoader mySubscriptionLoader; @Autowired private ISubscriptionDeliveryChannelNamer mySubscriptionDeliveryChannelNamer; @@ -90,8 +105,10 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base canonicalSubscription.setIdElement(new IdDt("test")); canonicalSubscription.setChannelType(CanonicalSubscriptionChannelType.RESTHOOK); mySubscriptionRegistry.unregisterAllSubscriptions(); - ourSubscribableChannel = mySubscriptionChannelFactory.newDeliveryChannel(mySubscriptionDeliveryChannelNamer.nameFromSubscription(canonicalSubscription)); - ourSubscribableChannel.subscribe(myStandaloneSubscriptionMessageHandler); + ourSubscribableChannel = mySubscriptionChannelFactory.newDeliveryReceivingChannel(mySubscriptionDeliveryChannelNamer.nameFromSubscription(canonicalSubscription), CONSUMER_OPTIONS); + ourSubscribableChannel.subscribe(mySubscriptionActivatingSubscriber); + ourSubscribableChannel.subscribe(subscriptionMatchingSubscriber); + ourSubscribableChannel.subscribe(subscriptionRegisteringSubscriber); myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, mySubscriptionMatchingPost); myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED, mySubscriptionActivatedPost); } @@ -115,7 +132,7 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base } protected void initSubscriptionLoader(List subscriptions, String uuid) throws InterruptedException { - myMockFhirClientSubscriptionProvider.setBundleProvider(new SimpleBundleProvider(new ArrayList<>(subscriptions), uuid)); +// myMockFhirClientSubscriptionProvider.setBundleProvider(new SimpleBundleProvider(new ArrayList<>(subscriptions), uuid)); mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); } @@ -165,7 +182,7 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context"; FhirContext context = ourListenerRestServer.getFhirContext(); //Preload structure definitions so the load doesn't happen during the test (first load can be a little slow) - context.getValidationSupport().fetchAllStructureDefinitions(context); + context.getValidationSupport().fetchAllStructureDefinitions(); } @AfterClass diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java deleted file mode 100755 index 24c58bc8e5b..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.server.SimpleBundleProvider; -import org.hl7.fhir.dstu3.model.Enumerations; -import org.hl7.fhir.dstu3.model.SearchParameter; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.Collections; - -import static org.junit.Assert.assertEquals; - -public class SearchParamLoaderTest extends BaseBlockingQueueSubscribableChannelDstu3Test { - private static final int MOCK_FHIR_CLIENT_FAILURES = 3; - @Autowired - private MockFhirClientSearchParamProvider myMockFhirClientSearchParamProvider; - @Autowired - private SearchParamRegistryImpl mySearchParamRegistry; - - @Before - public void setFailCount() { - myMockFhirClientSearchParamProvider.setFailCount(MOCK_FHIR_CLIENT_FAILURES); - } - - @After - public void restoreFailCount() { - myMockFhirClientSearchParamProvider.setFailCount(0); - } - - @Test - public void testSubscriptionLoaderFhirClientDown() { - String criteria = "BodySite?accessType=Catheter,PD%20Catheter"; - - SearchParameter sp = new SearchParameter(); - sp.addBase("BodySite"); - sp.setCode("accessType"); - sp.setType(Enumerations.SearchParamType.TOKEN); - sp.setExpression("BodySite.extension('BodySite#accessType')"); - sp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); - sp.setStatus(Enumerations.PublicationStatus.ACTIVE); - - IBundleProvider bundle = new SimpleBundleProvider(Collections.singletonList(sp), "uuid"); - initSearchParamRegistry(bundle); - assertEquals(0, myMockFhirClientSearchParamProvider.getFailCount()); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java deleted file mode 100644 index 31c859d1cb9..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandlerTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; -import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; -import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionMatchingSubscriber; -import org.hl7.fhir.dstu3.model.Subscription; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; - -public class StandaloneSubscriptionMessageHandlerTest extends BaseSubscriptionDstu3Test { - - @Autowired - StandaloneSubscriptionMessageHandler myStandaloneSubscriptionMessageHandler; - @Autowired - FhirContext myFhirContext; - @MockBean - SubscriptionMatchingSubscriber mySubscriptionMatchingSubscriber; - @MockBean - SubscriptionRegistry mySubscriptionRegistry; - - @Test - public void activeSubscriptionIsRegistered() { - Subscription subscription = makeActiveSubscription("testCriteria", "testPayload", "testEndpoint"); - ResourceModifiedMessage message = new ResourceModifiedMessage(myFhirContext, subscription, ResourceModifiedMessage.OperationTypeEnum.CREATE); - ResourceModifiedJsonMessage jsonMessage = new ResourceModifiedJsonMessage(message); - myStandaloneSubscriptionMessageHandler.handleMessage(jsonMessage); - Mockito.verify(mySubscriptionRegistry, never()).unregisterSubscription(any()); - Mockito.verify(mySubscriptionRegistry).registerSubscriptionUnlessAlreadyRegistered(any()); - Mockito.verify(mySubscriptionMatchingSubscriber).matchActiveSubscriptionsAndDeliver(any()); - } - - @Test - public void requestedSubscriptionNotRegistered() { - Subscription subscription = makeSubscriptionWithStatus("testCriteria", "testPayload", "testEndpoint", Subscription.SubscriptionStatus.REQUESTED); - ResourceModifiedMessage message = new ResourceModifiedMessage(myFhirContext, subscription, ResourceModifiedMessage.OperationTypeEnum.CREATE); - ResourceModifiedJsonMessage jsonMessage = new ResourceModifiedJsonMessage(message); - myStandaloneSubscriptionMessageHandler.handleMessage(jsonMessage); - Mockito.verify(mySubscriptionRegistry, never()).unregisterSubscription(any()); - Mockito.verify(mySubscriptionRegistry, never()).registerSubscriptionUnlessAlreadyRegistered(any()); - Mockito.verify(mySubscriptionMatchingSubscriber).matchActiveSubscriptionsAndDeliver(any()); - } - - @Test - public void deleteSubscription() { - Subscription subscription = makeSubscriptionWithStatus("testCriteria", "testPayload", "testEndpoint", Subscription.SubscriptionStatus.REQUESTED); - ResourceModifiedMessage message = new ResourceModifiedMessage(myFhirContext, subscription, ResourceModifiedMessage.OperationTypeEnum.DELETE); - ResourceModifiedJsonMessage jsonMessage = new ResourceModifiedJsonMessage(message); - myStandaloneSubscriptionMessageHandler.handleMessage(jsonMessage); - Mockito.verify(mySubscriptionRegistry).unregisterSubscription(any()); - Mockito.verify(mySubscriptionRegistry, never()).registerSubscriptionUnlessAlreadyRegistered(any()); - Mockito.verify(mySubscriptionMatchingSubscriber, never()).matchActiveSubscriptionsAndDeliver(any()); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java deleted file mode 100644 index 100f219de9f..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.module.standalone; - -import ca.uhn.fhir.rest.api.Constants; -import org.hl7.fhir.dstu3.model.Subscription; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscribableChannelDstu3Test { - - @Test - public void testSubscriptionLoaderFhirClient() throws InterruptedException { - String payload = "application/fhir+json"; - - String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; - String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; - - List subs = new ArrayList<>(); - subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase)); - subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase)); - - mySubscriptionActivatedPost.setExpectedCount(2); - initSubscriptionLoader(subs, "uuid"); - mySubscriptionActivatedPost.awaitExpected(); - - ourObservationListener.setExpectedCount(1); - sendObservation(myCode, "SNOMED-CT"); - ourObservationListener.awaitExpected(); - - waitForSize(0, ourCreatedObservations); - waitForSize(1, ourUpdatedObservations); - assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); - } - - @Test - public void testSubscriptionLoaderFhirClientSubscriptionNotActive() throws InterruptedException { - String payload = "application/fhir+json"; - - String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; - String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; - - List subs = new ArrayList<>(); - subs.add(makeActiveSubscription(criteria1, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); - subs.add(makeActiveSubscription(criteria2, payload, ourListenerServerBase).setStatus(Subscription.SubscriptionStatus.REQUESTED)); - - initSubscriptionLoader(subs, "uuid"); - - sendObservation(myCode, "SNOMED-CT"); - - waitForSize(0, ourCreatedObservations); - waitForSize(0, ourUpdatedObservations); - } -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessageTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessageTest.java index 008489a162d..73eaf13ad11 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessageTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessageTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidatorTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidatorTest.java index 236fd5f8448..c2dd000ab8e 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidatorTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidatorTest.java @@ -1,20 +1,40 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; -import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.match.deliver.websocket.WebsocketConnectionValidator; +import ca.uhn.fhir.jpa.subscription.match.deliver.websocket.WebsocketValidationResponse; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import org.hl7.fhir.r4.model.IdType; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.PlatformTransactionManager; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(SpringRunner.class) @@ -23,11 +43,24 @@ public class WebsocketConnectionValidatorTest { public static String WEBSOCKET_SUBSCRIPTION_ID = "2"; public static String NON_EXISTENT_SUBSCRIPTION_ID = "3"; - @Configuration - @ComponentScan("ca.uhn.fhir.jpa.subscription.module.subscriber.websocket") - public static class SpringConfig { - } - + @MockBean + MatchUrlService myMatchUrlService; + @MockBean + DaoRegistry myDaoRegistry; + @MockBean + PlatformTransactionManager myPlatformTransactionManager; + @MockBean + SearchParamMatcher mySearchParamMatcher; + @MockBean + SubscriptionChannelConfig mySubscriptionChannelConfig; + @MockBean + SubscriptionChannelFactory mySubscriptionChannelFactory; + @MockBean + IInterceptorBroadcaster myInterceptorBroadcaster; + @MockBean + InMemoryResourceMatcher myInMemoryResourceMatcher; + @MockBean + ISchedulerService mySchedulerService; @MockBean SubscriptionRegistry mySubscriptionRegistry; @@ -71,4 +104,38 @@ public class WebsocketConnectionValidatorTest { response = myWebsocketConnectionValidator.validate(idType); assertTrue(response.isValid()); } + + @Configuration + public static class SpringConfig extends SubscriptionProcessorConfig { + + @Bean + public DaoConfig daoConfig() { + return new DaoConfig(); + } + + @Bean + public PartitionSettings partitionSettings() { return new PartitionSettings(); } + + @Bean + public ModelConfig modelConfig() { + return new ModelConfig(); + } + + @Bean + public FhirContext fhirContext() { + return FhirContext.forR4(); + } + + @Bean("hapiJpaTaskExecutor") + public AsyncTaskExecutor taskExecutor() { + return mock(AsyncTaskExecutor.class); + } + + @Bean + public WebsocketConnectionValidator websocketConnectionValidator() { + return new WebsocketConnectionValidator(); + } + + + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoaderTest.java new file mode 100644 index 00000000000..f6d9d4c3761 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionSubmitInterceptorLoaderTest.java @@ -0,0 +1,91 @@ +package ca.uhn.fhir.jpa.subscription.submit.interceptor; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; +import org.hl7.fhir.dstu2.model.Subscription; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { + SubscriptionSubmitterConfig.class, + SearchParamConfig.class, + SubscriptionSubmitInterceptorLoaderTest.MyConfig.class +}) +public class SubscriptionSubmitInterceptorLoaderTest { + + @MockBean + private ISearchParamProvider mySearchParamProvider; + @MockBean + private ISchedulerService mySchedulerService; + @MockBean + private IInterceptorService myInterceptorService; + @MockBean + private IValidationSupport myValidationSupport; + @MockBean + private SubscriptionChannelFactory mySubscriptionChannelFactory; + @MockBean + private DaoRegistry myDaoRegistry; + @Autowired + private SubscriptionSubmitInterceptorLoader mySubscriptionSubmitInterceptorLoader; + @Autowired + private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; + + /** + * It should be possible to run only the {@link SubscriptionSubmitterConfig} without the + * {@link ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig} + */ + @Test + public void testLoaderCanRunWithoutProcessorConfigLoaded() { + verify(myInterceptorService, times(1)).registerInterceptor(eq(mySubscriptionMatcherInterceptor)); + } + + @Configuration + public static class MyConfig { + + @Bean + public FhirContext fhirContext() { + return FhirContext.forR4(); + } + + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } + + @Bean + public ModelConfig modelConfig() { + return new ModelConfig(); + } + + @Bean + public DaoConfig daoConfig() { + DaoConfig daoConfig = new DaoConfig(); + daoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); + return daoConfig; + } + + } + + +} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index e6234a4fec8..981ea8587b4 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml @@ -157,7 +157,7 @@ ca.uhn.hapi.fhir hapi-fhir-converter - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index e2a164287b9..119b7fefc37 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -2,12 +2,13 @@ package ca.uhn.fhirtest; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider; -import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; @@ -18,8 +19,8 @@ import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5; import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; @@ -28,6 +29,7 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhirtest.config.TestDstu2Config; import ca.uhn.fhirtest.config.TestDstu3Config; @@ -35,7 +37,6 @@ import ca.uhn.fhirtest.config.TestR4Config; import ca.uhn.fhirtest.config.TestR5Config; import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor; import org.apache.commons.lang3.StringUtils; -import ca.uhn.fhir.jpa.provider.GraphQLProvider; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -124,7 +125,7 @@ public class TestRestfulServer extends RestfulServer { providers.add(myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class)); systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); etagSupport = ETagSupportEnum.ENABLED; - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, myAppCtx.getBean(DaoConfig.class)); + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription(implDesc); setServerConformanceProvider(confProvider); providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); @@ -143,7 +144,7 @@ public class TestRestfulServer extends RestfulServer { providers.add(myAppCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class)); systemDao = myAppCtx.getBean("mySystemDaoR4", IFhirSystemDao.class); etagSupport = ETagSupportEnum.ENABLED; - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, myAppCtx.getBean(DaoConfig.class)); + JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription(implDesc); setServerConformanceProvider(confProvider); providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); @@ -162,7 +163,7 @@ public class TestRestfulServer extends RestfulServer { providers.add(myAppCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class)); systemDao = myAppCtx.getBean("mySystemDaoR5", IFhirSystemDao.class); etagSupport = ETagSupportEnum.ENABLED; - JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, myAppCtx.getBean(DaoConfig.class)); + JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription(implDesc); setServerConformanceProvider(confProvider); providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); @@ -198,6 +199,11 @@ public class TestRestfulServer extends RestfulServer { CorsInterceptor corsInterceptor = new CorsInterceptor(); registerInterceptor(corsInterceptor); + /* + * Enable FHIRPath evaluation + */ + registerInterceptor(new FhirPathFilterInterceptor()); + /* * Enable version conversion */ @@ -245,18 +251,12 @@ public class TestRestfulServer extends RestfulServer { */ setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); - /* - * Register subscription interceptors - */ - SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); - subscriptionInterceptorLoader.registerInterceptors(); - /* * Cascading deletes */ DaoRegistry daoRegistry = myAppCtx.getBean(DaoRegistry.class); IInterceptorBroadcaster interceptorBroadcaster = myAppCtx.getBean(IInterceptorBroadcaster.class); - CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(daoRegistry, interceptorBroadcaster); + CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, daoRegistry, interceptorBroadcaster); registerInterceptor(cascadingDeleteInterceptor); /* diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java index 60cebbab627..0075db72db9 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java @@ -1,5 +1,9 @@ package ca.uhn.fhirtest.config; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhirtest.interceptor.AnalyticsInterceptor; @@ -7,14 +11,19 @@ import ca.uhn.fhirtest.joke.HolyFooCowInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; @Configuration -@Import(WebsocketDispatcherConfig.class) +@Import({ + WebsocketDispatcherConfig.class, + SubscriptionChannelConfig.class, + SubscriptionProcessorConfig.class, + SubscriptionSubmitterConfig.class +}) public class CommonConfig { /** * Do some fancy logging to create a nice access log that has details about each incoming request. + * * @return */ @Bean @@ -22,7 +31,7 @@ public class CommonConfig { LoggingInterceptor retVal = new LoggingInterceptor(); retVal.setLoggerName("fhirtest.access"); retVal.setMessageFormat( - "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); + "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); retVal.setLogExceptions(true); retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}"); return retVal; @@ -37,17 +46,17 @@ public class CommonConfig { retVal.setAnalyticsTid("UA-1395874-6"); return retVal; } - + /** * This is a joke - * + *

        * https://chat.fhir.org/#narrow/stream/implementers/topic/Unsupported.20search.20parameters */ @Bean public IServerInterceptor holyFooCowInterceptor() { return new HolyFooCowInterceptor(); } - + /** * Do some fancy logging to create a nice access log that has details about each incoming request. */ @@ -60,7 +69,7 @@ public class CommonConfig { return retVal; } - public static boolean isLocalTestMode(){ + public static boolean isLocalTestMode() { return "true".equalsIgnoreCase(System.getProperty("testmode.local")); } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index fdb1dccca7d..64f9644f6b9 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect; @@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor; import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.PostgreSQL94Dialect; import org.hl7.fhir.dstu2.model.Subscription; import org.springframework.beans.factory.annotation.Value; @@ -41,14 +42,6 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { @Value(FHIR_LUCENE_LOCATION_DSTU2) private String myFhirLuceneLocation; - /** - * This lets the "@Value" fields reference properties from the properties file - */ - @Bean - public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { - return new PropertySourcesPlaceholderConfigurer(); - } - @Bean public PublicSecurityInterceptor securityInterceptor() { return new PublicSecurityInterceptor(); @@ -94,6 +87,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { retVal.setUsername(myDbUsername); retVal.setPassword(myDbPassword); retVal.setDefaultQueryTimeout(20); + retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE); return retVal; } @@ -146,12 +140,20 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { requestValidator.setFailOnSeverity(null); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorDstu2()); + requestValidator.addValidatorModule(instanceValidator()); requestValidator.setIgnoreValidatorExceptions(true); return requestValidator; } + /** + * This lets the "@Value" fields reference properties from the properties file + */ + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + // @Bean(autowire = Autowire.BY_TYPE) // public IServerInterceptor subscriptionSecurityInterceptor() { // return new SubscriptionsRequireManualActivationInterceptorDstu2(); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index eed61ff64af..733d846fd39 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; @@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor; import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.PostgreSQL94Dialect; import org.hl7.fhir.dstu2.model.Subscription; import org.springframework.beans.factory.annotation.Autowire; @@ -101,6 +102,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { retVal.setUsername(myDbUsername); retVal.setPassword(myDbPassword); retVal.setDefaultQueryTimeout(20); + retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE); return retVal; } @@ -146,7 +148,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { requestValidator.setFailOnSeverity(null); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorDstu3()); + requestValidator.addValidatorModule(instanceValidator()); requestValidator.setIgnoreValidatorExceptions(true); return requestValidator; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index 6b836aab1b4..af391e1f3dc 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; @@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor; import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.PostgreSQL94Dialect; import org.hl7.fhir.dstu2.model.Subscription; import org.springframework.beans.factory.annotation.Autowire; @@ -86,6 +87,7 @@ public class TestR4Config extends BaseJavaConfigR4 { retVal.setUsername(myDbUsername); retVal.setPassword(myDbPassword); retVal.setDefaultQueryTimeout(20); + retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE); return retVal; } @@ -140,7 +142,7 @@ public class TestR4Config extends BaseJavaConfigR4 { requestValidator.setFailOnSeverity(null); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorR4()); + requestValidator.addValidatorModule(instanceValidator()); requestValidator.setIgnoreValidatorExceptions(true); return requestValidator; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java index c71bfd1259c..53308e2213c 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigR5; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; @@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor; import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.PostgreSQL94Dialect; import org.hl7.fhir.dstu2.model.Subscription; import org.springframework.beans.factory.annotation.Autowire; @@ -86,6 +87,7 @@ public class TestR5Config extends BaseJavaConfigR5 { retVal.setUsername(myDbUsername); retVal.setPassword(myDbPassword); retVal.setDefaultQueryTimeout(20); + retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE); return retVal; } @@ -140,7 +142,7 @@ public class TestR5Config extends BaseJavaConfigR5 { requestValidator.setFailOnSeverity(null); requestValidator.setAddResponseHeaderOnSeverity(null); requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); - requestValidator.addValidatorModule(instanceValidatorR5()); + requestValidator.addValidatorModule(instanceValidator()); requestValidator.setIgnoreValidatorExceptions(true); return requestValidator; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html index dfc4769d6e4..fa0fa5666f6 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html @@ -40,10 +40,8 @@

        - - - This is not a production server! - + + This is not a production server! Do not store any information here that contains personal health information or any other confidential information. This server will be regularly purged and reloaded with fixed test data. diff --git a/hapi-fhir-narrativegenerator/src/main/java/ca/uhn/fhir/narrative/template/TemplateNarrativeGenerator.java b/hapi-fhir-narrativegenerator/src/main/java/ca/uhn/fhir/narrative/template/TemplateNarrativeGenerator.java index a1d6a35da3e..fcb7fea9c61 100644 --- a/hapi-fhir-narrativegenerator/src/main/java/ca/uhn/fhir/narrative/template/TemplateNarrativeGenerator.java +++ b/hapi-fhir-narrativegenerator/src/main/java/ca/uhn/fhir/narrative/template/TemplateNarrativeGenerator.java @@ -28,7 +28,7 @@ public class TemplateNarrativeGenerator implements INarrativeGenerator { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TemplateNarrativeGenerator.class); private FhirContext myFhirContext = FhirContext.forDstu3(); - private IValidationSupport myValidationSupport = new DefaultProfileValidationSupport(); + private IContextValidationSupport myValidationSupport = new DefaultProfileValidationSupport(); @Override public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) { diff --git a/hapi-fhir-osgi-core/pom.xml b/hapi-fhir-osgi-core/pom.xml deleted file mode 100644 index 35d30c0c7df..00000000000 --- a/hapi-fhir-osgi-core/pom.xml +++ /dev/null @@ -1,299 +0,0 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-deployable-pom - 3.0.0-SNAPSHOT - ../hapi-deployable-pom/pom.xml - - - hapi-fhir-osgi-core - bundle - - http://jamesagnew.github.io/hapi-fhir/ - - HAPI FHIR - OSGi Bundle - - - - ca.uhn.hapi.fhir - hapi-fhir-base - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-utilities - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-client - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-server - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu2 - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-structures-hl7org-dstu2 - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu3 - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-structures-r4 - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2 - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu3 - ${project.version} - - - - org.thymeleaf - thymeleaf - - - com.helger - ph-schematron - - - Saxon-HE - net.sf.saxon - - - - - com.helger - ph-commons - - - - javax.servlet - javax.servlet-api - provided - - - - - org.slf4j - slf4j-api - ${slf4j_target_version} - - - - - es.nitaur.markdown - txtmark - true - - - org.antlr - ST4 - true - - - xpp3 - xpp3 - true - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - **/ca/uhn/fhir/rest/server/interceptor/CorsInterceptor.java - - - - - - - maven-antrun-plugin - - - copySources - generate-sources - - run - - - - - - - - - - - - - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-source - generate-sources - - add-source - - - - target/sources - - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - ${project.artifactId} - - <_nouses>true - <_removeheaders>Built-By, Include-Resource, Private-Package, Require-Capability - - - - !ca.uhn.*, - !org.hl7.*, - com.ctc.wstx.api;version="4.4";resolution:=optional, - com.ctc.wstx.*;version="4.4";resolution:=optional, - com.google.*;resolution:=optional;-remove-attribute:=version, - com.helger.commons;resolution:=optional;-remove-attribute:=version, - com.helger.*;resolution:=optional;-remove-attribute:=version, - javassist;-remove-attribute:=version, - javax.*;-remove-attribute:=version, - net.sf.saxon;resolution:=optional, - org.apache.commons.*;-remove-attribute:=version, - org.apache.http.client.protocol;version="4.0", - org.apache.http.*;version="4.0", - org.codehaus.stax2;resolution:=optional;-remove-attribute:=version, - org.codehaus.stax2.*;resolution:=optional;-remove-attribute:=version, - org.oclc.purl.*;-remove-attribute:=version, - org.slf4j.*;-remove-attribute:=version, - org.xmlpull.v1;resolution:=optional, - * - - - - - - - bundle - - package - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - integration-test - verify - - integration-test - - - - - - - - src/main/resources - true - - - ../hapi-fhir-base/src/main/resources - true - - - ../hapi-fhir-structures-dstu/src/main/resources - false - - - ../hapi-fhir-structures-dstu/target/generated-resources/tinder - false - - - ../hapi-fhir-structures-dstu2/src/main/resources - false - - - ../hapi-fhir-structures-dstu2/target/generated-resources/tinder - false - - - ../hapi-fhir-structures-hl7org-dstu2/src/main/resources - false - - - ../hapi-fhir-structures-dstu3/src/main/resources - false - - - - ../hapi-fhir-validation-resources-dstu2/src/main/resources - false - - - ../hapi-fhir-validation-resources-dstu3/src/main/resources - false - - - - - - - - - diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirConfigurationException.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirConfigurationException.java deleted file mode 100644 index 5336f50f85e..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirConfigurationException.java +++ /dev/null @@ -1,50 +0,0 @@ -package ca.uhn.fhir.osgi; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -/** - * Exception thrown from the Spring-DM/OSGi wiring. These - * exceptions are thrown when an error was encountered - * that was caused by incorrect wiring. - * - * @author Akana, Inc. Professional Services - * - */ -public class FhirConfigurationException extends Exception { - - public FhirConfigurationException() { - super(); - } - - public FhirConfigurationException(String message) { - super(message); - } - - public FhirConfigurationException(Throwable cause) { - super(cause); - } - - public FhirConfigurationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirProviderBundle.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirProviderBundle.java deleted file mode 100644 index dbd5d28eed8..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirProviderBundle.java +++ /dev/null @@ -1,56 +0,0 @@ -package ca.uhn.fhir.osgi; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -import java.util.Collection; - -/** - * This is an abstraction for adding one or more Providers - * ("plain" providers as well as Resource Providers) - * to the configuration of a Fhir Server. This approach - * is needed versus direct publication of providers as - * OSGi services because references to OSGi services are - * really proxies that only implement the methods of the - * service's interfaces. This means that the introspection - * and annotation processing needed for HAPI FHIR provider - * processing is not possible on those proxy references.. - * - * To get around this restriction, instances of this interface - * will be published as OSGi services and the real providers - * will typically be Spring wired into the underlying bean. - * - * Beans that are decorated with this interface can be - * published as OSGi services and will be registered in - * the specified FHIR Server. The OSGi service definition - * should have the following entry: - * - * - * - * where the value matches the same - * assigned to a FhirServer OSGi service. -* - * @author Akana, Inc. Professional Services - * - */ -public interface FhirProviderBundle { - public Collection getProviders(); -} diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServer.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServer.java deleted file mode 100644 index 1561fe992b3..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServer.java +++ /dev/null @@ -1,87 +0,0 @@ -package ca.uhn.fhir.osgi; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -import java.util.Collection; - - -/** - * Instances of the FHIR Server must implement this interface - * in order to be registered as OSGi services capable of dynamic - * provider registration. It expected that implementations of this - * interface will also extend RestfulService. - * - * The OSGi service definition for instances of the FHIR SERver - * should have the following entry: - * - * - * - * where the value matches the same specified - * on the published "provider" OSGi services that are to be - * dynamically registered in the FHIR Server instance. - * - * @author Akana, Inc. Professional Services - * - */ -public interface FhirServer { - public static final String SVCPROP_SERVICE_NAME = "fhir.server.name"; - - /** - * Dynamically registers a single provider with the RestfulServer - * - * @param provider the provider to be registered - * @throws FhirConfigurationException - */ - public void registerOsgiProvider(Object provider) throws FhirConfigurationException; - - /** - * Dynamically unregisters a single provider with the RestfulServer - * - * @param provider the provider to be unregistered - * @throws FhirConfigurationException - */ - public void unregisterOsgiProvider(Object provider) throws FhirConfigurationException; - - /** - * Dynamically registers a list of providers with the RestfulServer - * - * @param provider the providers to be registered - * @throws FhirConfigurationException - */ - public void registerOsgiProviders(Collection provider) throws FhirConfigurationException; - - /** - * Dynamically unregisters a list of providers with the RestfulServer - * - * @param provider the providers to be unregistered - * @throws FhirConfigurationException - */ - public void unregisterOsgiProviders(Collection provider) throws FhirConfigurationException; - - /** - * Dynamically unregisters all of providers currently registered - * - * @throws FhirConfigurationException - */ - public void unregisterOsgiProviders() throws FhirConfigurationException; - -} diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServerImpl.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServerImpl.java deleted file mode 100644 index 2d10cb29d22..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/FhirServerImpl.java +++ /dev/null @@ -1,155 +0,0 @@ -package ca.uhn.fhir.osgi; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.server.RestfulServer; - -/** - * - * @author Akana, Inc. Professional Services - * - */ -public class FhirServerImpl extends RestfulServer implements FhirServer { - private static Logger log = LoggerFactory.getLogger(FhirServerImpl.class); - - private Collection serverProviders = Collections.synchronizedCollection(new ArrayList()); - - public FhirServerImpl() { - super(); - } - - public FhirServerImpl(FhirContext theCtx) { - super(theCtx); - } - - /** - * Dynamically registers a single provider with the RestfulServer - * - * @param provider the provider to be registered - * @throws FhirConfigurationException - */ - @Override - public void registerOsgiProvider (Object provider) throws FhirConfigurationException { - if (null == provider) { - throw new NullPointerException("FHIR Provider cannot be null"); - } - try { - super.registerProvider(provider); - log.trace("registered provider. class ["+provider.getClass().getName()+"]"); - this.serverProviders.add(provider); - } catch (Exception e) { - log.error("Error registering FHIR Provider", e); - throw new FhirConfigurationException("Error registering FHIR Provider", e); - } - } - - /** - * Dynamically unregisters a single provider with the RestfulServer - * - * @param provider the provider to be unregistered - * @throws FhirConfigurationException - */ - @Override - public void unregisterOsgiProvider (Object provider) throws FhirConfigurationException { - if (null == provider) { - throw new NullPointerException("FHIR Provider cannot be null"); - } - try { - this.serverProviders.remove(provider); - log.trace("unregistered provider. class ["+provider.getClass().getName()+"]"); - super.unregisterProvider(provider); - } catch (Exception e) { - log.error("Error unregistering FHIR Provider", e); - throw new FhirConfigurationException("Error unregistering FHIR Provider", e); - } - } - - /** - * Dynamically registers a list of providers with the RestfulServer - * - * @param provider the providers to be registered - * @throws FhirConfigurationException - */ - @Override - public void registerOsgiProviders (Collection providers) throws FhirConfigurationException { - if (null == providers) { - throw new NullPointerException("FHIR Provider list cannot be null"); - } - try { - super.registerProviders(providers); - for (Object provider : providers) { - log.trace("registered provider. class ["+provider.getClass().getName()+"]"); - this.serverProviders.add(provider); - } - } catch (Exception e) { - log.error("Error registering FHIR Providers", e); - throw new FhirConfigurationException("Error registering FHIR Providers", e); - } - } - - /** - * Dynamically unregisters a list of providers with the RestfulServer - * - * @param provider the providers to be unregistered - * @throws FhirConfigurationException - */ - @Override - public void unregisterOsgiProviders (Collection providers) throws FhirConfigurationException { - if (null == providers) { - throw new NullPointerException("FHIR Provider list cannot be null"); - } - try { - for (Object provider : providers) { - log.trace("unregistered provider. class ["+provider.getClass().getName()+"]"); - this.serverProviders.remove(provider); - } - super.unregisterProvider(providers); - } catch (Exception e) { - log.error("Error unregistering FHIR Providers", e); - throw new FhirConfigurationException("Error unregistering FHIR Providers", e); - } - } - - /** - * Dynamically unregisters all of providers currently registered - * - * @throws FhirConfigurationException - */ - @Override - public void unregisterOsgiProviders () throws FhirConfigurationException { - // need to make a copy to be able to remove items - Collection providers = new ArrayList(); - providers.addAll(this.serverProviders); - this.unregisterOsgiProviders(providers); - } - -} diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/SimpleFhirProviderBundle.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/SimpleFhirProviderBundle.java deleted file mode 100644 index 1a2ac16af95..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/SimpleFhirProviderBundle.java +++ /dev/null @@ -1,56 +0,0 @@ -package ca.uhn.fhir.osgi; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -import java.util.Collection; - -/** - * - * @author Akana, Inc. Professional Services - * - */ -public class SimpleFhirProviderBundle implements FhirProviderBundle { - - // ///////////////////////////////////// - // //////// Spring Wiring //////// - // ///////////////////////////////////// - - private Collection providers; - - public void setProviders (Collection providers) { - this.providers = providers; - } - - // ///////////////////////////////////// - // ///////////////////////////////////// - // ///////////////////////////////////// - - public SimpleFhirProviderBundle () { - super(); - } - - @Override - public Collection getProviders () { - return this.providers; - } - -} diff --git a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/impl/FhirServerManager.java b/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/impl/FhirServerManager.java deleted file mode 100644 index df63ed4deee..00000000000 --- a/hapi-fhir-osgi-core/src/main/java/ca/uhn/fhir/osgi/impl/FhirServerManager.java +++ /dev/null @@ -1,301 +0,0 @@ -package ca.uhn.fhir.osgi.impl; - -/* - * #%L - * HAPI FHIR - OSGi Bundle - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ca.uhn.fhir.osgi.FhirConfigurationException; -import ca.uhn.fhir.osgi.FhirProviderBundle; -import ca.uhn.fhir.osgi.FhirServer; - -/** - * Manage the dynamic registration of FHIR Servers and FHIR Providers. - * Methods on this Spring Bean will be invoked from OSGi Reference - * Listeners when OSGi services are published for these interfaces. - * - * @author Akana, Inc. Professional Services - * - */ -public class FhirServerManager { - - private static Logger log = LoggerFactory.getLogger(FhirServerManager.class); - private static final String FIRST_SERVER = "#first"; - - private Map registeredServers = new ConcurrentHashMap(); - private Map>> serverProviders = new ConcurrentHashMap>>(); - private Collection> registeredProviders = Collections.synchronizedList(new ArrayList>()); - private Map>> pendingProviders = new ConcurrentHashMap>>(); - private boolean haveDefaultProviders = false; - - /** - * Register a new FHIR Server OSGi service. - * We need to track these services so we can find the correct - * server to use when registering/unregistering providers. - *

        - * The OSGi service definition of a FHIR Server should look like: - *

        -	 * <osgi:service ref="some.bean" interface="ca.uhn.fhir.osgi.FhirServer">
        -	 *     <osgi:service-properties>
        -	 *         <entry key="name" value="osgi-service-name"/>
        -	 *         <entry key="fhir.server.name" value="fhir-server-name"/>
        -	 *     </osgi:service-properties>	
        -	 * </osgi:service>
        -	 * 
        - * The fhir-server-name parameter is also specified for all - * of the FHIR Providers that are to be dynamically registered with the - * named FHIR Server. - * - * @param server OSGi service implementing the FhirService interface - * @param props the for that service - * - * @throws FhirConfigurationException - */ - public void registerFhirServer (FhirServer server, Map props) throws FhirConfigurationException { - if (server != null) { - String serviceName = (String)props.get("name"); - if (null == serviceName) { - serviceName = ""; - } - String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); - if (serverName != null) { - if (registeredServers.containsKey(serverName)) { - throw new FhirConfigurationException("FHIR Server named ["+serverName+"] is already registered. These names must be unique."); - } - log.trace("Registering FHIR Server ["+serverName+"]. (OSGi service named ["+serviceName+"])"); - registeredServers.put(serverName, server); - if (haveDefaultProviders && registeredServers.size() > 1) { - throw new FhirConfigurationException("FHIR Providers are registered without a server name. Only one FHIR Server is allowed."); - } - Collection> providers = pendingProviders.get(serverName); - if (providers != null) { - log.trace("Registering FHIR providers waiting for this server to be registered."); - pendingProviders.remove(serverName); - for (Collection list : providers) { - this.registerProviders(list, server, serverName); - } - } - if (registeredServers.size() == 1) { - providers = pendingProviders.get(FIRST_SERVER); - if (providers != null) { - log.trace("Registering FHIR providers waiting for the first/only server to be registered."); - pendingProviders.remove(FIRST_SERVER); - for (Collection list : providers) { - this.registerProviders(list, server, serverName); - } - } - } - } else { - throw new FhirConfigurationException("FHIR Server registered in OSGi is missing the required ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); - } - } - } - - /** - * This method will be called when a FHIR Server OSGi service - * is being removed from the container. This normally will only - * occur when its bundle is stopped because it is being removed - * or updated. - * - * @param server OSGi service implementing the FhirService interface - * @param props the for that service - * - * @throws FhirConfigurationException - */ - public void unregisterFhirServer (FhirServer server, Map props) throws FhirConfigurationException { - if (server != null) { - String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); - if (serverName != null) { - FhirServer service = registeredServers.get(serverName); - if (service != null) { - log.trace("Unregistering FHIR Server ["+serverName+"]"); - service.unregisterOsgiProviders(); - registeredServers.remove(serverName); - log.trace("Dequeue any FHIR providers waiting for this server"); - pendingProviders.remove(serverName); - if (registeredServers.size() == 0) { - log.trace("Dequeue any FHIR providers waiting for the first/only server"); - pendingProviders.remove(FIRST_SERVER); - } - Collection> providers = serverProviders.get(serverName); - if (providers != null) { - serverProviders.remove(serverName); - registeredProviders.removeAll(providers); - } - } - } else { - throw new FhirConfigurationException("FHIR Server registered in OSGi is missing the required ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); - } - } - } - - /** - * Register a new FHIR Provider-Bundle OSGi service. - * - * This could be a "plain" provider that is published with the - * FhirProvider interface or it could be a resource provider that - * is published with either that same interface or the IResourceProvider - * interface. - * - * (That check is not made here but is included as usage documentation) - * - *

        - * The OSGi service definition of a FHIR Provider would look like: - *

        -	 * <osgi:service ref="some.bean" interface="ca.uhn.fhir.osgi.IResourceProvider">
        -	 *     <osgi:service-properties>
        -	 *         <entry key="name" value="osgi-service-name"/>
        -	 *         <entry key="fhir.server.name" value="fhir-server-name"/>
        -	 *     </osgi:service-properties>	
        -	 * </osgi:service>
        -	 * 
        - * The fhir-server-name parameter is the value assigned to the - * fhir.server.name service-property of one of the OSGi-published - * FHIR Servers. - * - * @param server OSGi service implementing a FHIR provider interface - * @param props the for that service - * - * @throws FhirConfigurationException - */ - public void registerFhirProviders (FhirProviderBundle bundle, Map props) throws FhirConfigurationException { - if (bundle != null) { - Collection providers = bundle.getProviders(); - if (providers != null && !providers.isEmpty()) { - try { - String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); - String ourServerName = getServerName(serverName); - String bundleName = (String)props.get("name"); - if (null == bundleName) { - bundleName = ""; - } - log.trace("Register FHIR Provider Bundle ["+bundleName+"] on FHIR Server ["+ourServerName+"]"); - FhirServer server = registeredServers.get(ourServerName); - if (server != null) { - registerProviders(providers, server, serverName); - } else { - log.trace("Queue the Provider Bundle waiting for FHIR Server to be registered"); - Collection> pending; - synchronized(pendingProviders) { - pending = pendingProviders.get(serverName); - if (null == pending) { - pending = Collections.synchronizedCollection(new ArrayList>()); - pendingProviders.put(serverName, pending); - } - } - pending.add(providers); - } - - } catch (BadServerException e) { - throw new FhirConfigurationException("Unable to register the OSGi FHIR Provider. Multiple Restful Servers exist. Specify the ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); - } - } - } - } - - protected void registerProviders (Collection providers, FhirServer server, String serverName) throws FhirConfigurationException { - server.registerOsgiProviders(providers); - - Collection> active; - synchronized(serverProviders) { - active = serverProviders.get(serverName); - if (null == active) { - active = Collections.synchronizedCollection(new ArrayList>()); - serverProviders.put(serverName, active); - } - } - active.add(providers); - registeredProviders.add(providers); - } - - /** - * This method will be called when a FHIR Provider OSGi service - * is being removed from the container. This normally will only - * occur when its bundle is stopped because it is being removed - * or updated. - * - * @param server OSGi service implementing one of the provider - * interfaces - * @param props the for that service - * - * @throws FhirConfigurationException - */ - public void unregisterFhirProviders (FhirProviderBundle bundle, Map props) throws FhirConfigurationException { - if (bundle != null) { - Collection providers = bundle.getProviders(); - if (providers != null && !providers.isEmpty()) { - try { - registeredProviders.remove(providers); - String serverName = (String)props.get(FhirServer.SVCPROP_SERVICE_NAME); - String ourServerName = getServerName(serverName); - FhirServer server = registeredServers.get(ourServerName); - if (server != null) { - - server.unregisterOsgiProviders(providers); - - Collection> active = serverProviders.get(serverName); - if (active != null) { - active.remove(providers); - } - } - } catch (BadServerException e) { - throw new FhirConfigurationException("Unable to register the OSGi FHIR Provider. Multiple Restful Servers exist. Specify the ["+FhirServer.SVCPROP_SERVICE_NAME+"] service-property"); - } - } - } - } - - /* - * Adjust the FHIR Server name allowing for null which would - * indicate that the Provider should be registered with the - * only FHIR Server defined. - */ - private String getServerName (String osgiName) throws BadServerException { - String result = osgiName; - if (null == result) { - if (registeredServers.isEmpty()) { // wait for the first one - haveDefaultProviders = true; // only allow one server - result = FIRST_SERVER; - } else - if (registeredServers.size() == 1) { // use the only one - haveDefaultProviders = true; // only allow one server - result = registeredServers.keySet().iterator().next(); - } else { - throw new BadServerException(); - } - } - return result; - } - - class BadServerException extends Exception { - BadServerException() { - super(); - } - } - -} diff --git a/hapi-fhir-osgi-core/src/main/resources/META-INF/spring/hapi-fhir-osgi.xml b/hapi-fhir-osgi-core/src/main/resources/META-INF/spring/hapi-fhir-osgi.xml deleted file mode 100644 index cdb1329067c..00000000000 --- a/hapi-fhir-osgi-core/src/main/resources/META-INF/spring/hapi-fhir-osgi.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/hapi-fhir-osgi-core/src/test/java/ca/uhn/fhir/osgi/ManifestIT.java b/hapi-fhir-osgi-core/src/test/java/ca/uhn/fhir/osgi/ManifestIT.java deleted file mode 100644 index 4c45a21e8dc..00000000000 --- a/hapi-fhir-osgi-core/src/test/java/ca/uhn/fhir/osgi/ManifestIT.java +++ /dev/null @@ -1,26 +0,0 @@ -package ca.uhn.fhir.osgi; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; - -import java.io.FileReader; - -import org.apache.commons.io.IOUtils; -import org.junit.Test; - -public class ManifestIT { - - /** - * See #234 - */ - @Test - public void testValidateManifest() throws Exception { - - FileReader r = new FileReader("./target/classes/META-INF/MANIFEST.MF"); - String file = IOUtils.toString(r); - - assertThat(file, not(containsString(".jar=/"))); - } - -} diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 7e17a896660..2234e21fd10 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java index 10c982a0439..85f5cca6989 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java @@ -462,6 +462,15 @@ public abstract class RequestDetails { if (myRequestContents == null) { myRequestContents = getByteStreamRequestContents(); } + return getRequestContentsIfLoaded(); + } + + /** + * Returns the request contents if they were loaded, returns null otherwise + * + * @see #loadRequestContents() + */ + public byte[] getRequestContentsIfLoaded() { return myRequestContents; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java index eb574295f62..b6e2652543b 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.server; +import java.net.URI; + /* * #%L * HAPI FHIR - Server Framework @@ -20,68 +22,137 @@ package ca.uhn.fhir.rest.server; * #L% */ +import java.util.Optional; + import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.ServletServerHttpRequest; + +import static java.util.Optional.ofNullable; + +import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy; + /** - * Works like the normal {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's an x-forwarded-host present, in which case that's used in place of the server's address. - *

        - * If the Apache Http Server mod_proxy isn't configured to supply x-forwarded-proto, the factory method that you use to create the address strategy will determine the default. Note that - * mod_proxy doesn't set this by default, but it can be configured via RequestHeader set X-Forwarded-Proto http (or https) - *

        - *

        - * If you want to set the protocol based on something other than the constructor argument, you should be able to do so by overriding protocol. - *

        - *

        - * Note that while this strategy was designed to work with Apache Http Server, and has been tested against it, it should work with any proxy server that sets x-forwarded-host + * Works like the normal + * {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's + * an x-forwarded-host present, in which case that's used in place of the + * server's address. + *

        + * If the Apache Http Server mod_proxy isn't configured to supply + * x-forwarded-proto, the factory method that you use to create the + * address strategy will determine the default. Note that mod_proxy + * doesn't set this by default, but it can be configured via + * RequestHeader set X-Forwarded-Proto http (or https) + *

        + *

        + * List of supported forward headers: + *

          + *
        • x-forwarded-host - original host requested by the client throw proxy + * server + *
        • x-forwarded-proto - original protocol (http, https) requested by the + * client + *
        • x-forwarded-port - original port request by the client, assume default + * port if not defined + *
        • x-forwarded-prefix - original server prefix / context path requested by + * the client + *
        + *

        + *

        + * If you want to set the protocol based on something other than the constructor + * argument, you should be able to do so by overriding protocol. + *

        + *

        + * Note that while this strategy was designed to work with Apache Http Server, + * and has been tested against it, it should work with any proxy server that + * sets x-forwarded-host *

        * - * @author Created by Bill de Beaubien on 3/30/2015. */ public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy { - private boolean myUseHttps = false; + private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix"; + private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; + private static final String X_FORWARDED_HOST = "x-forwarded-host"; + private static final String X_FORWARDED_PORT = "x-forwarded-port"; - protected ApacheProxyAddressStrategy(boolean theUseHttps) { - myUseHttps = theUseHttps; + private static final Logger LOG = LoggerFactory + .getLogger(ApacheProxyAddressStrategy.class); + + private final boolean useHttps; + + /** + * @param useHttps + * Is used when the {@code x-forwarded-proto} is not set in the + * request. + */ + public ApacheProxyAddressStrategy(boolean useHttps) { + this.useHttps = useHttps; } @Override - public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) { - String forwardedHost = getForwardedHost(theRequest); - if (forwardedHost != null) { - return forwardedServerBase(theServletContext, theRequest, forwardedHost); - } - return super.determineServerBase(theServletContext, theRequest); + public String determineServerBase(ServletContext servletContext, + HttpServletRequest request) { + String serverBase = super.determineServerBase(servletContext, request); + ServletServerHttpRequest requestWrapper = new ServletServerHttpRequest( + request); + HttpHeaders headers = requestWrapper.getHeaders(); + Optional forwardedHost = headers + .getValuesAsList(X_FORWARDED_HOST).stream().findFirst(); + return forwardedHost + .map(s -> forwardedServerBase(serverBase, headers, s)) + .orElse(serverBase); } - public String forwardedServerBase(ServletContext theServletContext, HttpServletRequest theRequest, String theForwardedHost) { - String serverBase = super.determineServerBase(theServletContext, theRequest); - String host = theRequest.getHeader("host"); - if (host != null) { - serverBase = serverBase.replace(host, theForwardedHost); - serverBase = serverBase.substring(serverBase.indexOf("://")); - return protocol(theRequest) + serverBase; - } - return serverBase; + private String forwardedServerBase(String originalServerBase, + HttpHeaders headers, String forwardedHost) { + Optional forwardedPrefix = getForwardedPrefix(headers); + LOG.debug("serverBase: {}, forwardedHost: {}, forwardedPrefix: {}", + originalServerBase, forwardedHost, forwardedPrefix); + LOG.debug("request header: {}", headers); + + String host = protocol(headers) + "://" + forwardedHost; + String hostWithOptionalPort = port(headers).map(p -> (host + ":" + p)) + .orElse(host); + + String path = forwardedPrefix + .orElseGet(() -> pathFrom(originalServerBase)); + return joinStringsWith(hostWithOptionalPort, path, "/"); } - private String getForwardedHost(HttpServletRequest theRequest) { - String forwardedHost = theRequest.getHeader("x-forwarded-host"); - if (forwardedHost != null) { - int commaPos = forwardedHost.indexOf(','); - if (commaPos >= 0) { - forwardedHost = forwardedHost.substring(0, commaPos - 1); - } - } - return forwardedHost; + private Optional port(HttpHeaders headers) { + return ofNullable(headers.getFirst(X_FORWARDED_PORT)); } - protected String protocol(HttpServletRequest theRequest) { - String protocol = theRequest.getHeader("x-forwarded-proto"); + private String pathFrom(String serverBase) { + String serverBasePath = URI.create(serverBase).getPath(); + return StringUtils.defaultIfBlank(serverBasePath, ""); + } + + private static String joinStringsWith(String left, String right, + String joiner) { + if (left.endsWith(joiner) && right.startsWith(joiner)) { + return left + right.substring(1); + } else if (left.endsWith(joiner) || right.startsWith(joiner)) { + return left + right; + } else { + return left + joiner + right; + } + } + + private Optional getForwardedPrefix(HttpHeaders headers) { + return ofNullable(headers.getFirst(X_FORWARDED_PREFIX)); + } + + private String protocol(HttpHeaders headers) { + String protocol = headers.getFirst(X_FORWARDED_PROTO); if (protocol != null) { return protocol; } - return myUseHttps ? "https" : "http"; + return useHttps ? "https" : "http"; } /** diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ResourceBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ResourceBinding.java index 0d264258bfe..04695bc0957 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ResourceBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ResourceBinding.java @@ -25,9 +25,10 @@ import java.util.List; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.method.MethodMatchEnum; /** - * Created by dsotnikov on 2/25/2014. + * Holds all method bindings for an individual resource type */ public class ResourceBinding { @@ -36,9 +37,16 @@ public class ResourceBinding { private String resourceName; private List> myMethodBindings = new ArrayList<>(); + /** + * Constructor + */ public ResourceBinding() { + super(); } + /** + * Constructor + */ public ResourceBinding(String resourceName, List> methods) { this.resourceName = resourceName; this.myMethodBindings = methods; @@ -51,14 +59,28 @@ public class ResourceBinding { } ourLog.debug("Looking for a handler for {}", theRequest); + + /* + * Look for the method with the highest match strength + */ + + BaseMethodBinding matchedMethod = null; + MethodMatchEnum matchedMethodStrength = null; + for (BaseMethodBinding rm : myMethodBindings) { - if (rm.incomingServerRequestMatchesMethod(theRequest)) { - ourLog.debug("Handler {} matches", rm); - return rm; + MethodMatchEnum nextMethodMatch = rm.incomingServerRequestMatchesMethod(theRequest); + if (nextMethodMatch != MethodMatchEnum.NONE) { + if (matchedMethodStrength == null || matchedMethodStrength.ordinal() < nextMethodMatch.ordinal()) { + matchedMethod = rm; + matchedMethodStrength = nextMethodMatch; + } + if (matchedMethodStrength == MethodMatchEnum.EXACT) { + break; + } } - ourLog.trace("Handler {} does not match", rm); } - return null; + + return matchedMethod; } public String getResourceName() { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index 3c32507105a..b4361907e96 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -45,6 +45,7 @@ import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.ConformanceMethodBinding; +import ca.uhn.fhir.rest.server.method.MethodMatchEnum; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy; import ca.uhn.fhir.util.*; @@ -298,7 +299,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer resourceMethod = null; String resourceName = requestDetails.getResourceName(); - if (myServerConformanceMethod.incomingServerRequestMatchesMethod(requestDetails)) { + if (myServerConformanceMethod.incomingServerRequestMatchesMethod(requestDetails) != MethodMatchEnum.NONE) { resourceMethod = myServerConformanceMethod; } else if (resourceName == null) { resourceBinding = myServerBinding; @@ -1785,6 +1786,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer theProviders) { + while (theProviders.size() > 0) { + unregisterProvider(theProviders.get(0)); + } + } + + private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException { theResponse.setStatus(theException.getStatusCode()); addHeadersToResponse(theResponse); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 0ef51233a04..e3a46d5ebad 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -22,6 +22,8 @@ package ca.uhn.fhir.rest.server; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; @@ -29,6 +31,7 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.PreferHeader; import ca.uhn.fhir.rest.api.PreferReturnEnum; @@ -42,9 +45,11 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.method.ElementsParameter; import ca.uhn.fhir.rest.server.method.SummaryEnumParameter; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.BinaryUtil; import ca.uhn.fhir.util.DateUtils; import ca.uhn.fhir.util.UrlUtil; +import com.google.common.collect.Sets; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseReference; @@ -73,7 +78,8 @@ public class RestfulServerUtils { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class); private static final HashSet TEXT_ENCODE_ELEMENTS = new HashSet<>(Arrays.asList("*.text", "*.id", "*.meta", "*.(mandatory)")); - private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap()); + private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap<>()); + private static EnumSet ourOperationsWhichAllowPreferHeader = EnumSet.of(RestOperationTypeEnum.CREATE, RestOperationTypeEnum.UPDATE, RestOperationTypeEnum.PATCH); private enum NarrativeModeEnum { NORMAL, ONLY, SUPPRESS; @@ -163,7 +169,7 @@ public class RestfulServerUtils { } if (summaryModeCount) { - parser.setEncodeElements(Collections.singleton("Bundle.total")); + parser.setEncodeElements(Sets.newHashSet("Bundle.total", "Bundle.type")); } else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); parser.setEncodeElementsAppliesToChildResourcesOnly(true); @@ -692,59 +698,55 @@ public class RestfulServerUtils { return retVal; } - private static EnumSet ourOperationsWhichAllowPreferHeader = EnumSet.of(RestOperationTypeEnum.CREATE, RestOperationTypeEnum.UPDATE, RestOperationTypeEnum.PATCH); - public static boolean respectPreferHeader(RestOperationTypeEnum theRestOperationType) { return ourOperationsWhichAllowPreferHeader.contains(theRestOperationType); } @Nonnull - public static PreferHeader parsePreferHeader(IRestfulServer theServer, String theValue) { - PreferHeader retVal = new PreferHeader(); - - if (isNotBlank(theValue)) { - StringTokenizer tok = new StringTokenizer(theValue, ";"); - while (tok.hasMoreTokens()) { - String next = trim(tok.nextToken()); - int eqIndex = next.indexOf('='); - - String key; - String value; - if (eqIndex == -1 || eqIndex >= next.length() - 2) { - key = next; - value = ""; - } else { - key = next.substring(0, eqIndex).trim(); - value = next.substring(eqIndex + 1).trim(); - } - - if (key.equals(Constants.HEADER_PREFER_RETURN)) { - - if (value.length() < 2) { - continue; - } - if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) { - value = value.substring(1, value.length() - 1); - } - - retVal.setReturn(PreferReturnEnum.fromHeaderValue(value)); - - } else if (key.equals(Constants.HEADER_PREFER_RESPOND_ASYNC)) { - - retVal.setRespondAsync(true); - - } - } - } + public static PreferHeader parsePreferHeader(IRestfulServer theServer, String theValue) { + PreferHeader retVal = new PreferHeader(); + + if (isNotBlank(theValue)) { + StringTokenizer tok = new StringTokenizer(theValue, ";"); + while (tok.hasMoreTokens()) { + String next = trim(tok.nextToken()); + int eqIndex = next.indexOf('='); + + String key; + String value; + if (eqIndex == -1 || eqIndex >= next.length() - 2) { + key = next; + value = ""; + } else { + key = next.substring(0, eqIndex).trim(); + value = next.substring(eqIndex + 1).trim(); + } + + if (key.equals(Constants.HEADER_PREFER_RETURN)) { + + if (value.length() < 2) { + continue; + } + if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) { + value = value.substring(1, value.length() - 1); + } + + retVal.setReturn(PreferReturnEnum.fromHeaderValue(value)); + + } else if (key.equals(Constants.HEADER_PREFER_RESPOND_ASYNC)) { + + retVal.setRespondAsync(true); + + } + } + } if (retVal.getReturn() == null && theServer != null && theServer.getDefaultPreferReturn() != null) { retVal.setReturn(theServer.getDefaultPreferReturn()); } return retVal; - } - - + } public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) { @@ -768,12 +770,12 @@ public class RestfulServerUtils { } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set theSummaryMode, int stausCode, boolean theAddContentLocationHeader, - boolean respondGzip, RequestDetails theRequestDetails) throws IOException { + boolean respondGzip, RequestDetails theRequestDetails) throws IOException { return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null); } public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set theSummaryMode, int theStatusCode, String theStatusMessage, - boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType theOperationResourceLastUpdated) + boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType theOperationResourceLastUpdated) throws IOException { IRestfulResponse response = theRequestDetails.getResponse(); @@ -892,6 +894,19 @@ public class RestfulServerUtils { String charset = Constants.CHARSET_NAME_UTF8; Writer writer = response.getResponseWriter(theStatusCode, theStatusMessage, contentType, charset, respondGzip); + + // Interceptor call: SERVER_OUTGOING_WRITER_CREATED + if (theServer.getInterceptorService() != null && theServer.getInterceptorService().hasHooks(Pointcut.SERVER_OUTGOING_WRITER_CREATED)) { + HookParams params = new HookParams() + .add(Writer.class, writer) + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails); + Object newWriter = theServer.getInterceptorService().callHooksAndReturnObject(Pointcut.SERVER_OUTGOING_WRITER_CREATED, params); + if (newWriter != null) { + writer = (Writer) newWriter; + } + } + if (theResource == null) { // No response is being returned } else if (encodingDomainResourceAsText && theResource instanceof IResource) { @@ -937,5 +952,22 @@ public class RestfulServerUtils { } + /** + * @since 5.0.0 + */ + public static DeleteCascadeModeEnum extractDeleteCascadeParameter(RequestDetails theRequest) { + if (theRequest != null) { + String[] cascadeParameters = theRequest.getParameters().get(Constants.PARAMETER_CASCADE_DELETE); + if (cascadeParameters != null && Arrays.asList(cascadeParameters).contains(Constants.CASCADE_DELETE)) { + return DeleteCascadeModeEnum.DELETE; + } + String cascadeHeader = theRequest.getHeader(Constants.HEADER_CASCADE); + if (Constants.CASCADE_DELETE.equals(cascadeHeader)) { + return DeleteCascadeModeEnum.DELETE; + } + } + + return DeleteCascadeModeEnum.NONE; + } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java new file mode 100644 index 00000000000..5273dc5b2cc --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java @@ -0,0 +1,90 @@ +package ca.uhn.fhir.rest.server.interceptor; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.fhirpath.FhirPathExecutionException; +import ca.uhn.fhir.fhirpath.IFhirPath; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.ResponseDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.ParametersUtil; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * This interceptor looks for a URL parameter on requests called _fhirpath and + * replaces the resource being returned with a Parameters resource containing the results of + * the given FHIRPath expression evaluated against the resource that would otherwise + * have been returned. + * + * @see Interceptors - Response Customization: Evaluate FHIRPath + * @since 5.0.0 + */ +public class FhirPathFilterInterceptor { + + @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) + public void preProcessOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails) { + IBaseResource responseResource = theResponseDetails.getResponseResource(); + if (responseResource != null) { + String[] fhirPathParams = theRequestDetails.getParameters().get(Constants.PARAM_FHIRPATH); + if (fhirPathParams != null) { + + FhirContext ctx = theRequestDetails.getFhirContext(); + IBaseParameters responseParameters = ParametersUtil.newInstance(ctx); + + for (String expression : fhirPathParams) { + if (isNotBlank(expression)) { + IBase resultPart = ParametersUtil.addParameterToParameters(ctx, responseParameters, "result"); + ParametersUtil.addPartString(ctx, resultPart, "expression", expression); + + IFhirPath fhirPath = ctx.newFhirPath(); + List outputs; + try { + outputs = fhirPath.evaluate(responseResource, expression, IBase.class); + } catch (FhirPathExecutionException e) { + throw new InvalidRequestException("Error parsing FHIRPath expression: " + e.getMessage()); + } + + for (IBase nextOutput : outputs) { + if (nextOutput instanceof IBaseResource) { + ParametersUtil.addPartResource(ctx, resultPart, "result", (IBaseResource) nextOutput); + } else { + ParametersUtil.addPart(ctx, resultPart, "result", nextOutput); + } + } + } + } + + theResponseDetails.setResponseResource(responseParameters); + } + } + } + +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index c1b9055897f..86f54c122fb 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -55,7 +55,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank; * Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use * {@link InterceptorAdapter} in order to not need to implement every method. *

        - * See: See the server + * See: See the server * interceptor documentation for more information on how to use this class. *

        * Note that unless otherwise stated, it is possible to throw any subclass of diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java index a3995b7d5b6..3c413192152 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java @@ -23,10 +23,12 @@ package ca.uhn.fhir.rest.server.interceptor; public class InterceptorOrders { public static final int SERVE_MEDIA_RESOURCE_RAW_INTERCEPTOR = 1000; - public static final int RESPONSE_HIGHLIGHTER_INTERCEPTOR = 10000; + public static final int RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED = -1; - /** Non instantiable */ + /** + * Non instantiable + */ private InterceptorOrders() { // nothing } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java new file mode 100644 index 00000000000..e5756769cc5 --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java @@ -0,0 +1,150 @@ +package ca.uhn.fhir.rest.server.interceptor; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.apache.commons.lang3.Validate; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * This interceptor captures and makes + * available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response + * stream for FHIR responses. + *

        + * Response details are made available in the request {@link RequestDetails#getUserData() RequestDetails UserData map} + * with {@link #RESPONSE_RESULT_KEY} as the key. + *

        + * + * @since 5.0.0 + */ +public class ResponseSizeCapturingInterceptor { + + /** + * If the response was a character stream, a character count will be placed in the + * {@link RequestDetails#getUserData() RequestDetails UserData map} with this key, containing + * an {@link Result} value. + *

        + * The value will be placed at the start of the {@link Pointcut#SERVER_PROCESSING_COMPLETED} pointcut, so it will not + * be available before that time. + *

        + */ + public static final String RESPONSE_RESULT_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_RESPONSE_RESULT_KEY"; + + private static final String COUNTING_WRITER_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_COUNTING_WRITER_KEY"; + private List> myConsumers = new ArrayList<>(); + + @Hook(Pointcut.SERVER_OUTGOING_WRITER_CREATED) + public Writer capture(RequestDetails theRequestDetails, Writer theWriter) { + CountingWriter retVal = new CountingWriter(theWriter); + theRequestDetails.getUserData().put(COUNTING_WRITER_KEY, retVal); + return retVal; + } + + + @Hook(value = Pointcut.SERVER_PROCESSING_COMPLETED, order = InterceptorOrders.RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED) + public void completed(RequestDetails theRequestDetails) { + CountingWriter countingWriter = (CountingWriter) theRequestDetails.getUserData().get(COUNTING_WRITER_KEY); + if (countingWriter != null) { + int charCount = countingWriter.getCount(); + Result result = new Result(theRequestDetails, charCount); + notifyConsumers(result); + + theRequestDetails.getUserData().put(RESPONSE_RESULT_KEY, result); + } + } + + /** + * Registers a new consumer. All consumers will be notified each time a request is complete. + * + * @param theConsumer The consumer + */ + public void registerConsumer(@Nonnull Consumer theConsumer) { + Validate.notNull(theConsumer); + myConsumers.add(theConsumer); + } + + private void notifyConsumers(Result theResult) { + myConsumers.forEach(t -> t.accept(theResult)); + } + + /** + * Contains the results of the capture + */ + public static class Result { + private final int myWrittenChars; + + public RequestDetails getRequestDetails() { + return myRequestDetails; + } + + private final RequestDetails myRequestDetails; + + public Result(RequestDetails theRequestDetails, int theWrittenChars) { + myRequestDetails = theRequestDetails; + myWrittenChars = theWrittenChars; + } + + public int getWrittenChars() { + return myWrittenChars; + } + + } + + + private static class CountingWriter extends Writer { + + private final Writer myWrap; + private int myCount; + + private CountingWriter(Writer theWrap) { + myWrap = theWrap; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + myCount += len; + myWrap.write(cbuf, off, len); + } + + @Override + public void flush() throws IOException { + myWrap.flush(); + } + + @Override + public void close() throws IOException { + myWrap.close(); + } + + public int getCount() { + return myCount; + } + } + +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java index 6301bc9eaea..3bbacdcf283 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java @@ -53,7 +53,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * has permission to perform the given action. *

        * See the HAPI FHIR - * Documentation on Server Security + * Documentation on Server Security * for information on how to use this interceptor. *

        * diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/SearchNarrowingInterceptor.java_703256810379985 b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/SearchNarrowingInterceptor.java_703256810379985 new file mode 100644 index 00000000000..3ede68f6b6a --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/SearchNarrowingInterceptor.java_703256810379985 @@ -0,0 +1,278 @@ +package ca.uhn.fhir.rest.server.interceptor.auth; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.rest.api.QualifiedParamList; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.ParameterUtil; +import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; +import ca.uhn.fhir.rest.server.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails; +import ca.uhn.fhir.rest.server.util.ServletRequestUtil; +import ca.uhn.fhir.util.BundleUtil; +import ca.uhn.fhir.util.bundle.ModifiableBundleEntry; +import com.google.common.collect.ArrayListMultimap; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.function.Consumer; + +/** + * This interceptor can be used to automatically narrow the scope of searches in order to + * automatically restrict the searches to specific compartments. + *

        + * For example, this interceptor + * could be used to restrict a user to only viewing data belonging to Patient/123 (i.e. data + * in the Patient/123 compartment). In this case, a user performing a search + * for
        + * http://baseurl/Observation?category=laboratory
        + * would receive results as though they had requested
        + * http://baseurl/Observation?subject=Patient/123&category=laboratory + *

        + *

        + * Note that this interceptor should be used in combination with {@link AuthorizationInterceptor} + * if you are restricting results because of a security restriction. This interceptor is not + * intended to be a failsafe way of preventing users from seeing the wrong data (that is the + * purpose of AuthorizationInterceptor). This interceptor is simply intended as a convenience to + * help users simplify their queries while not receiving security errors for to trying to access + * data they do not have access to see. + *

        + * + * @see AuthorizationInterceptor + */ +public class SearchNarrowingInterceptor { + private static final Logger ourLog = LoggerFactory.getLogger(SearchNarrowingInterceptor.class); + + + /** + * Subclasses should override this method to supply the set of compartments that + * the user making the request should actually have access to. + *

        + * Typically this is done by examining theRequestDetails to find + * out who the current user is and then building a list of Strings. + *

        + * + * @param theRequestDetails The individual request currently being applied + * @return The list of allowed compartments and instances that should be used + * for search narrowing. If this method returns null, no narrowing will + * be performed + */ + protected AuthorizedList buildAuthorizedList(@SuppressWarnings("unused") RequestDetails theRequestDetails) { + return null; + } + + @Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED) + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + // We don't support this operation type yet + Validate.isTrue(theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_SYSTEM); + + if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_TYPE) { + return true; + } + + FhirContext ctx = theRequestDetails.getServer().getFhirContext(); + RuntimeResourceDefinition resDef = ctx.getResourceDefinition(theRequestDetails.getResourceName()); + HashMap> parameterToOrValues = new HashMap<>(); + AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails); + if (authorizedList == null) { + return true; + } + + /* + * Create a map of search parameter values that need to be added to the + * given request + */ + Collection compartments = authorizedList.getAllowedCompartments(); + if (compartments != null) { + processResourcesOrCompartments(theRequestDetails, resDef, parameterToOrValues, compartments, true); + } + Collection resources = authorizedList.getAllowedInstances(); + if (resources != null) { + processResourcesOrCompartments(theRequestDetails, resDef, parameterToOrValues, resources, false); + } + + /* + * Add any param values to the actual request + */ + if (parameterToOrValues.size() > 0) { + Map newParameters = new HashMap<>(theRequestDetails.getParameters()); + for (Map.Entry> nextEntry : parameterToOrValues.entrySet()) { + String nextParamName = nextEntry.getKey(); + List nextAllowedValues = nextEntry.getValue(); + + if (!newParameters.containsKey(nextParamName)) { + + /* + * If we don't already have a parameter of the given type, add one + */ + String nextValuesJoined = ParameterUtil.escapeAndJoinOrList(nextAllowedValues); + String[] paramValues = {nextValuesJoined}; + newParameters.put(nextParamName, paramValues); + + } else { + + /* + * If the client explicitly requested the given parameter already, we'll + * just update the request to have the intersection of the values that the client + * requested, and the values that the user is allowed to see + */ + String[] existingValues = newParameters.get(nextParamName); + boolean restrictedExistingList = false; + for (int i = 0; i < existingValues.length; i++) { + + String nextExistingValue = existingValues[i]; + List nextRequestedValues = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextExistingValue); + List nextPermittedValues = ListUtils.intersection(nextRequestedValues, nextAllowedValues); + if (nextPermittedValues.size() > 0) { + restrictedExistingList = true; + existingValues[i] = ParameterUtil.escapeAndJoinOrList(nextPermittedValues); + } + + } + + /* + * If none of the values that were requested by the client overlap at all + * with the values that the user is allowed to see, we'll just add the permitted + * list as a new list. Ultimately this scenario actually means that the client + * shouldn't get *any* results back, and adding a new AND parameter (that doesn't + * overlap at all with the others) is one way of ensuring that. + */ + if (!restrictedExistingList) { + String[] newValues = Arrays.copyOf(existingValues, existingValues.length + 1); + newValues[existingValues.length] = ParameterUtil.escapeAndJoinOrList(nextAllowedValues); + newParameters.put(nextParamName, newValues); + } + } + + } + theRequestDetails.setParameters(newParameters); + } + + return true; + } + + @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED) + public void incomingRequestPreHandled(ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.TRANSACTION) { + return; + } + + IBaseBundle bundle = (IBaseBundle) theRequestDetails.getResource(); + FhirContext ctx = theRequestDetails.getFhirContext(); + BundleEntryUrlProcessor processor = new BundleEntryUrlProcessor(ctx, theRequestDetails, theRequest, theResponse); + BundleUtil.processEntries(ctx, bundle, processor); + } + + private class BundleEntryUrlProcessor implements Consumer { + private final FhirContext myFhirContext; + private final ServletRequestDetails myRequestDetails; + private final HttpServletRequest myRequest; + private final HttpServletResponse myResponse; + + public BundleEntryUrlProcessor(FhirContext theFhirContext, ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) { + myFhirContext = theFhirContext; + myRequestDetails = theRequestDetails; + myRequest = theRequest; + myResponse = theResponse; + } + + @Override + public void accept(ModifiableBundleEntry theModifiableBundleEntry) { + ArrayListMultimap paramValues = ArrayListMultimap.create(); + + String url = theModifiableBundleEntry.getRequestUrl(); + + ServletSubRequestDetails subServletRequestDetails = ServletRequestUtil.getServletSubRequestDetails(myRequestDetails, url, paramValues); + BaseMethodBinding method = subServletRequestDetails.getServer().determineResourceMethod(subServletRequestDetails, url); + RestOperationTypeEnum restOperationType = method.getRestOperationType(); + subServletRequestDetails.setRestOperationType(restOperationType); + + incomingRequestPostProcessed(subServletRequestDetails, myRequest, myResponse); + + theModifiableBundleEntry.setRequestUrl(myFhirContext, ServletRequestUtil.extractUrl(subServletRequestDetails)); + } + } + + private void processResourcesOrCompartments(RequestDetails theRequestDetails, RuntimeResourceDefinition theResDef, HashMap> theParameterToOrValues, Collection theResourcesOrCompartments, boolean theAreCompartments) { + String lastCompartmentName = null; + String lastSearchParamName = null; + for (String nextCompartment : theResourcesOrCompartments) { + Validate.isTrue(StringUtils.countMatches(nextCompartment, '/') == 1, "Invalid compartment name (must be in form \"ResourceType/xxx\": %s", nextCompartment); + String compartmentName = nextCompartment.substring(0, nextCompartment.indexOf('/')); + + String searchParamName = null; + if (compartmentName.equalsIgnoreCase(lastCompartmentName)) { + + // Avoid doing a lookup for the same thing repeatedly + searchParamName = lastSearchParamName; + + } else { + + if (compartmentName.equalsIgnoreCase(theRequestDetails.getResourceName())) { + + searchParamName = "_id"; + + } else if (theAreCompartments) { + + List searchParams = theResDef.getSearchParamsForCompartmentName(compartmentName); + if (searchParams.size() > 0) { + + // Resources like Observation have several fields that add the resource to + // the compartment. In the case of Observation, it's subject, patient and performer. + // For this kind of thing, we'll prefer the one called "patient". + RuntimeSearchParam searchParam = + searchParams + .stream() + .filter(t -> t.getName().equalsIgnoreCase(compartmentName)) + .findFirst() + .orElse(searchParams.get(0)); + searchParamName = searchParam.getName(); + + } + } + + lastCompartmentName = compartmentName; + lastSearchParamName = searchParamName; + + } + + if (searchParamName != null) { + List orValues = theParameterToOrValues.computeIfAbsent(searchParamName, t -> new ArrayList<>()); + orValues.add(nextCompartment); + } + } + } + +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.java new file mode 100644 index 00000000000..d1abbd993a5 --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/partition/RequestTenantPartitionInterceptor.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.rest.server.interceptor.partition; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy; + +import javax.annotation.Nonnull; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * This interceptor uses the request tenant ID (as supplied to the server using + * {@link ca.uhn.fhir.rest.server.RestfulServer#setTenantIdentificationStrategy(ITenantIdentificationStrategy)} + * to indicate the partition ID. With this interceptor registered, The server treats the tenant name + * supplied by the {@link ITenantIdentificationStrategy tenant identification strategy} as a partition name. + *

        + * Partition names (aka tenant IDs) must be registered in advance using the partition management operations. + *

        + * + * @since 5.0.0 + */ +@Interceptor +public class RequestTenantPartitionInterceptor { + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) + public RequestPartitionId PartitionIdentifyCreate(ServletRequestDetails theRequestDetails) { + return extractPartitionIdFromRequest(theRequestDetails); + } + + @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) + public RequestPartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) { + return extractPartitionIdFromRequest(theRequestDetails); + } + + @Nonnull + private RequestPartitionId extractPartitionIdFromRequest(ServletRequestDetails theRequestDetails) { + + // We will use the tenant ID that came from the request as the partition name + String tenantId = theRequestDetails.getTenantId(); + if (isBlank(tenantId)) { + throw new InternalErrorException("No tenant ID has been specified"); + } + + return RequestPartitionId.fromPartitionName(tenantId); + } + + +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java index 19ec2bcced3..c346050a0dd 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java @@ -26,47 +26,44 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IRestfulServer; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.server.BundleProviders; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ReflectionUtil; -import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import javax.annotation.Nonnull; import java.io.IOException; -import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isBlank; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; public abstract class BaseMethodBinding { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class); + private final List myQueryParameters; private FhirContext myContext; private Method myMethod; private List myParameters; private Object myProvider; private boolean mySupportsConditional; private boolean mySupportsConditionalMultiple; - public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { assert theMethod != null; assert theContext != null; @@ -75,6 +72,11 @@ public abstract class BaseMethodBinding { myContext = theContext; myProvider = theProvider; myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getRestOperationType()); + myQueryParameters = myParameters + .stream() + .filter(t -> t instanceof BaseQueryParameter) + .map(t -> (BaseQueryParameter) t) + .collect(Collectors.toList()); for (IParameter next : myParameters) { if (next instanceof ConditionalParamBinder) { @@ -90,6 +92,10 @@ public abstract class BaseMethodBinding { myMethod.setAccessible(true); } + protected List getQueryParameters() { + return myQueryParameters; + } + protected Object[] createMethodParams(RequestDetails theRequest) { Object[] params = new Object[getParameters().size()]; for (int i = 0; i < getParameters().size(); i++) { @@ -211,7 +217,7 @@ public abstract class BaseMethodBinding { return getRestOperationType(); } - public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest); + public abstract MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest); public abstract Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBinding.java index ba9fe65d7c4..aaed5731a6a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBinding.java @@ -111,20 +111,20 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding allowableRequestTypes = provideAllowableRequestTypes(); RequestTypeEnum requestType = theRequest.getRequestType(); if (!allowableRequestTypes.contains(requestType)) { - return false; + return MethodMatchEnum.NONE; } if (!getResourceName().equals(theRequest.getResourceName())) { - return false; + return MethodMatchEnum.NONE; } if (getMatchingOperation() == null && StringUtils.isNotBlank(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } if (getMatchingOperation() != null && !getMatchingOperation().equals(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } /* @@ -137,7 +137,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding { } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (Constants.OPERATION_NAME_GRAPHQL.equals(theRequest.getOperation())) { - return true; + return MethodMatchEnum.EXACT; } - return false; + return MethodMatchEnum.NONE; } @Override diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java index f378ec57128..9a92fc2e163 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java @@ -51,7 +51,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { private final Integer myIdParamIndex; private final RestOperationTypeEnum myResourceOperationType; - private String myResourceName; + private final String myResourceName; public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider); @@ -105,30 +105,36 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { // ObjectUtils.equals is replaced by a JDK7 method.. @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } if (theRequest.getResourceName() == null) { - return myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM; + if (myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM) { + return MethodMatchEnum.EXACT; + } else { + return MethodMatchEnum.NONE; + } } if (!StringUtils.equals(theRequest.getResourceName(), myResourceName)) { - return false; + return MethodMatchEnum.NONE; } boolean haveIdParam = theRequest.getId() != null && !theRequest.getId().isEmpty(); boolean wantIdParam = myIdParamIndex != null; if (haveIdParam != wantIdParam) { - return false; + return MethodMatchEnum.NONE; } if (theRequest.getId() == null) { - return myResourceOperationType == RestOperationTypeEnum.HISTORY_TYPE; + if (myResourceOperationType != RestOperationTypeEnum.HISTORY_TYPE) { + return MethodMatchEnum.NONE; + } } else if (theRequest.getId().hasVersionIdPart()) { - return false; + return MethodMatchEnum.NONE; } - return true; + return MethodMatchEnum.EXACT; } @@ -179,7 +185,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (isBlank(nextResource.getIdElement().getVersionIdPart()) && nextResource instanceof IResource) { //TODO: Use of a deprecated method should be resolved. - IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource); + IdDt versionId = ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource); if (versionId == null || versionId.isEmpty()) { throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))"); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/IncludeParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/IncludeParameter.java index 6e3cb7fa1fe..2dfc38d4fc7 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/IncludeParameter.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/IncludeParameter.java @@ -106,6 +106,11 @@ class IncludeParameter extends BaseQueryParameter { return null; } + @Override + protected boolean supportsRepetition() { + return myInstantiableCollectionType != null; + } + @Override public boolean handlesMissing() { return true; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodMatchEnum.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodMatchEnum.java new file mode 100644 index 00000000000..b5f31a12716 --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodMatchEnum.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.rest.server.method; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 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 enum MethodMatchEnum { + + // Order these from worst to best! + + NONE, + APPROXIMATE, + EXACT; + + public MethodMatchEnum weakerOf(MethodMatchEnum theOther) { + if (this.ordinal() < theOther.ordinal()) { + return this; + } else { + return theOther; + } + } + +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java index 87b648840bf..13a8ff92215 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java @@ -36,7 +36,6 @@ import ca.uhn.fhir.rest.server.method.ResourceParameter.Mode; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ReflectionUtil; -import ca.uhn.fhir.util.ValidateUtil; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -144,7 +143,7 @@ public class MethodUtil { parameter.setRequired(true); parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes()); parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes()); - parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist()); + parameter.setChainLists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist()); parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; @@ -154,12 +153,12 @@ public class MethodUtil { parameter.setRequired(false); parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes()); parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes()); - parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist()); + parameter.setChainLists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist()); parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; } else if (nextAnnotation instanceof RawParam) { - param = new RawParamsParmeter(parameters); + param = new RawParamsParameter(parameters); } else if (nextAnnotation instanceof IncludeParam) { Class> instantiableCollectionType; Class specType; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java index aa4b536bd1f..ee5f810d031 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java @@ -226,43 +226,43 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (isBlank(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } if (!myName.equals(theRequest.getOperation())) { if (!myName.equals(WILDCARD_NAME)) { - return false; + return MethodMatchEnum.NONE; } } if (getResourceName() == null) { if (isNotBlank(theRequest.getResourceName())) { if (!isGlobalMethod()) { - return false; + return MethodMatchEnum.NONE; } } } if (getResourceName() != null && !getResourceName().equals(theRequest.getResourceName())) { - return false; + return MethodMatchEnum.NONE; } RequestTypeEnum requestType = theRequest.getRequestType(); if (requestType != RequestTypeEnum.GET && requestType != RequestTypeEnum.POST) { // Operations can only be invoked with GET and POST - return false; + return MethodMatchEnum.NONE; } boolean requestHasId = theRequest.getId() != null; if (requestHasId) { - return myCanOperateAtInstanceLevel; + return myCanOperateAtInstanceLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE; } if (isNotBlank(theRequest.getResourceName())) { - return myCanOperateAtTypeLevel; + return myCanOperateAtTypeLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE; } - return myCanOperateAtServerLevel; + return myCanOperateAtServerLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE; } @Override diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PageMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PageMethodBinding.java index 5b0bd9355af..7bcf3756694 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PageMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PageMethodBinding.java @@ -174,12 +174,16 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { String[] pageId = theRequest.getParameters().get(Constants.PARAM_PAGINGACTION); if (pageId == null || pageId.length == 0 || isBlank(pageId[0])) { - return false; + return MethodMatchEnum.NONE; } - return theRequest.getRequestType() == RequestTypeEnum.GET; + if (theRequest.getRequestType() != RequestTypeEnum.GET) { + return MethodMatchEnum.NONE; + } + + return MethodMatchEnum.EXACT; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchMethodBinding.java index e68351bd167..c04e01a7ee9 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchMethodBinding.java @@ -80,9 +80,9 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { - boolean retVal = super.incomingServerRequestMatchesMethod(theRequest); - if (retVal) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { + MethodMatchEnum retVal = super.incomingServerRequestMatchesMethod(theRequest); + if (retVal.ordinal() > MethodMatchEnum.NONE.ordinal()) { PatchTypeParameter.getTypeForRequestOrThrowInvalidRequestException(theRequest); } return retVal; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParmeter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParameter.java similarity index 90% rename from hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParmeter.java rename to hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParameter.java index 1d8e992cad7..a74dba97398 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParmeter.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/RawParamsParameter.java @@ -35,11 +35,11 @@ import ca.uhn.fhir.rest.param.QualifierDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class RawParamsParmeter implements IParameter { +public class RawParamsParameter implements IParameter { private final List myAllMethodParameters; - public RawParamsParmeter(List theParameters) { + public RawParamsParameter(List theParameters) { myAllMethodParameters = theParameters; } @@ -53,7 +53,7 @@ public class RawParamsParmeter implements IParameter { continue; } - QualifierDetails qualifiers = SearchMethodBinding.extractQualifiersFromParameterName(nextName); + QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(nextName); boolean alreadyCaptured = false; for (IParameter nextParameter : myAllMethodParameters) { @@ -70,7 +70,7 @@ public class RawParamsParmeter implements IParameter { if (!alreadyCaptured) { if (retVal == null) { - retVal = new HashMap>(); + retVal = new HashMap<>(); } retVal.put(nextName, Arrays.asList(theRequest.getParameters().get(nextName))); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java index facae61707d..c0265121e70 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java @@ -51,6 +51,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -113,46 +114,62 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (!theRequest.getResourceName().equals(getResourceName())) { - return false; + return MethodMatchEnum.NONE; } for (String next : theRequest.getParameters().keySet()) { if (!next.startsWith("_")) { - return false; + return MethodMatchEnum.NONE; } } if (theRequest.getId() == null) { - return false; + return MethodMatchEnum.NONE; } if (mySupportsVersion == false) { if (theRequest.getId().hasVersionIdPart()) { - return false; + return MethodMatchEnum.NONE; } } if (isNotBlank(theRequest.getCompartmentName())) { - return false; + return MethodMatchEnum.NONE; } if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.HEAD ) { ourLog.trace("Method {} doesn't match because request type is not GET or HEAD: {}", theRequest.getId(), theRequest.getRequestType()); - return false; + return MethodMatchEnum.NONE; } if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { if (mySupportsVersion == false) { - return false; + return MethodMatchEnum.NONE; } else if (theRequest.getId().hasVersionIdPart() == false) { - return false; + return MethodMatchEnum.NONE; } } else if (!StringUtils.isBlank(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } - return true; + return MethodMatchEnum.EXACT; } @Override public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { IIdType requestId = theRequest.getId(); + FhirContext ctx = theRequest.getServer().getFhirContext(); + + String[] invalidQueryStringParams = new String[]{Constants.PARAM_CONTAINED, Constants.PARAM_COUNT, Constants.PARAM_INCLUDE, Constants.PARAM_REVINCLUDE, Constants.PARAM_SORT, Constants.PARAM_SEARCH_TOTAL_MODE}; + List invalidQueryStringParamsInRequest = new ArrayList<>(); + Set queryStringParamsInRequest = theRequest.getParameters().keySet(); + + for (String queryStringParamName : queryStringParamsInRequest) { + String lowercaseQueryStringParamName = queryStringParamName.toLowerCase(); + if (StringUtils.startsWithAny(lowercaseQueryStringParamName, invalidQueryStringParams)) { + invalidQueryStringParamsInRequest.add(queryStringParamName); + } + } + + if (!invalidQueryStringParamsInRequest.isEmpty()) { + throw new InvalidRequestException(ctx.getLocalizer().getMessage(ReadMethodBinding.class, "invalidParamsInRequest", invalidQueryStringParamsInRequest)); + } theMethodParams[myIdIndex] = ParameterUtil.convertIdToType(requestId, myIdParameterType); @@ -201,7 +218,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding { } } // if we have at least 1 result - + return retVal; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java index b5692b75463..7d54efd256d 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java @@ -41,7 +41,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import javax.annotation.Nonnull; import java.lang.reflect.Method; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -61,11 +65,13 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } private final String myResourceProviderResourceName; - private String myCompartmentName; + private final List myRequiredParamNames; + private final List myOptionalParamNames; + private final String myCompartmentName; private String myDescription; - private Integer myIdParamIndex; - private String myQueryName; - private boolean myAllowUnknownParams; + private final Integer myIdParamIndex; + private final String myQueryName; + private final boolean myAllowUnknownParams; public SearchMethodBinding(Class theReturnResourceType, Class theResourceProviderResourceType, Method theMethod, FhirContext theContext, Object theProvider) { super(theReturnResourceType, theMethod, theContext, theProvider); @@ -98,6 +104,17 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { this.myResourceProviderResourceName = null; } + myRequiredParamNames = getQueryParameters() + .stream() + .filter(t -> t.isRequired()) + .map(t -> t.getName()) + .collect(Collectors.toList()); + myOptionalParamNames = getQueryParameters() + .stream() + .filter(t -> !t.isRequired()) + .map(t -> t.getName()) + .collect(Collectors.toList()); + } public String getDescription() { @@ -129,122 +146,129 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (theRequest.getId() != null && myIdParamIndex == null) { ourLog.trace("Method {} doesn't match because ID is not null: {}", getMethod(), theRequest.getId()); - return false; + return MethodMatchEnum.NONE; } if (theRequest.getRequestType() == RequestTypeEnum.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) { ourLog.trace("Method {} doesn't match because request type is GET but operation is not null: {}", theRequest.getId(), theRequest.getOperation()); - return false; + return MethodMatchEnum.NONE; } if (theRequest.getRequestType() == RequestTypeEnum.POST && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) { ourLog.trace("Method {} doesn't match because request type is POST but operation is not _search: {}", theRequest.getId(), theRequest.getOperation()); - return false; + return MethodMatchEnum.NONE; } if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.POST) { ourLog.trace("Method {} doesn't match because request type is {}", getMethod(), theRequest.getRequestType()); - return false; + return MethodMatchEnum.NONE; } if (!StringUtils.equals(myCompartmentName, theRequest.getCompartmentName())) { ourLog.trace("Method {} doesn't match because it is for compartment {} but request is compartment {}", getMethod(), myCompartmentName, theRequest.getCompartmentName()); - return false; + return MethodMatchEnum.NONE; } if (theRequest.getParameters().get(Constants.PARAM_PAGINGACTION) != null) { - return false; + return MethodMatchEnum.NONE; } - // This is used to track all the parameters so we can reject queries that - // have additional params we don't understand - Set methodParamsTemp = new HashSet<>(); - - Set unqualifiedNames = theRequest.getUnqualifiedToQualifiedNames().keySet(); - Set qualifiedParamNames = theRequest.getParameters().keySet(); - for (IParameter nextParameter : getParameters()) { - if (!(nextParameter instanceof BaseQueryParameter)) { - continue; - } - BaseQueryParameter nextQueryParameter = (BaseQueryParameter) nextParameter; - String name = nextQueryParameter.getName(); - if (nextQueryParameter.isRequired()) { - - if (qualifiedParamNames.contains(name)) { - QualifierDetails qualifiers = extractQualifiersFromParameterName(name); - if (qualifiers.passes(nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist())) { - methodParamsTemp.add(name); - } - } - if (unqualifiedNames.contains(name)) { - List qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(name); - qualifiedNames = processWhitelistAndBlacklist(qualifiedNames, nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist()); - methodParamsTemp.addAll(qualifiedNames); - } - if (!qualifiedParamNames.contains(name) && !unqualifiedNames.contains(name)) { - ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), name); - return false; - } - - } else { - if (qualifiedParamNames.contains(name)) { - QualifierDetails qualifiers = extractQualifiersFromParameterName(name); - if (qualifiers.passes(nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist())) { - methodParamsTemp.add(name); - } - } - if (unqualifiedNames.contains(name)) { - List qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(name); - qualifiedNames = processWhitelistAndBlacklist(qualifiedNames, nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist()); - methodParamsTemp.addAll(qualifiedNames); - } - if (!qualifiedParamNames.contains(name)) { - methodParamsTemp.add(name); - } - } - } if (myQueryName != null) { String[] queryNameValues = theRequest.getParameters().get(Constants.PARAM_QUERY); if (queryNameValues != null && StringUtils.isNotBlank(queryNameValues[0])) { String queryName = queryNameValues[0]; if (!myQueryName.equals(queryName)) { ourLog.trace("Query name does not match {}", myQueryName); - return false; + return MethodMatchEnum.NONE; } - methodParamsTemp.add(Constants.PARAM_QUERY); } else { ourLog.trace("Query name does not match {}", myQueryName); - return false; + return MethodMatchEnum.NONE; } } else { String[] queryNameValues = theRequest.getParameters().get(Constants.PARAM_QUERY); if (queryNameValues != null && StringUtils.isNotBlank(queryNameValues[0])) { ourLog.trace("Query has name"); - return false; + return MethodMatchEnum.NONE; } } - for (String next : theRequest.getParameters().keySet()) { - if (next.startsWith("_") && !SPECIAL_SEARCH_PARAMS.contains(truncModifierPart(next))) { - methodParamsTemp.add(next); - } - } - Set keySet = theRequest.getParameters().keySet(); - if (myAllowUnknownParams == false) { - for (String next : keySet) { - if (!methodParamsTemp.contains(next)) { - return false; + Set unqualifiedNames = theRequest.getUnqualifiedToQualifiedNames().keySet(); + Set qualifiedParamNames = theRequest.getParameters().keySet(); + + MethodMatchEnum retVal = MethodMatchEnum.EXACT; + for (String nextRequestParam : theRequest.getParameters().keySet()) { + String nextUnqualifiedRequestParam = ParameterUtil.stripModifierPart(nextRequestParam); + if (nextRequestParam.startsWith("_") && !SPECIAL_SEARCH_PARAMS.contains(nextUnqualifiedRequestParam)) { + continue; + } + + boolean parameterMatches = false; + boolean approx = false; + for (BaseQueryParameter nextMethodParam : getQueryParameters()) { + + if (nextRequestParam.equals(nextMethodParam.getName())) { + QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(nextRequestParam); + if (qualifiers.passes(nextMethodParam.getQualifierWhitelist(), nextMethodParam.getQualifierBlacklist())) { + parameterMatches = true; + } + } else if (nextUnqualifiedRequestParam.equals(nextMethodParam.getName())) { + List qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(nextUnqualifiedRequestParam); + if (passesWhitelistAndBlacklist(qualifiedNames, nextMethodParam.getQualifierWhitelist(), nextMethodParam.getQualifierBlacklist())) { + parameterMatches = true; + } + } + + // Repetitions supplied by URL but not supported by this parameter + if (theRequest.getParameters().get(nextRequestParam).length > 1 != nextMethodParam.supportsRepetition()) { + approx = true; + } + + } + + + if (parameterMatches) { + + if (approx) { + retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE); + } + + } else { + + if (myAllowUnknownParams) { + retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE); + } else { + retVal = retVal.weakerOf(MethodMatchEnum.NONE); + } + + } + + if (retVal == MethodMatchEnum.NONE) { + break; + } + + } + + if (retVal != MethodMatchEnum.NONE) { + for (String nextRequiredParamName : myRequiredParamNames) { + if (!qualifiedParamNames.contains(nextRequiredParamName)) { + if (!unqualifiedNames.contains(nextRequiredParamName)) { + retVal = MethodMatchEnum.NONE; + break; + } } } } - return true; - } - - private String truncModifierPart(String param) { - int indexOfSeparator = param.indexOf(":"); - if (indexOfSeparator != -1) { - return param.substring(0, indexOfSeparator); + if (retVal != MethodMatchEnum.NONE) { + for (String nextRequiredParamName : myOptionalParamNames) { + if (!qualifiedParamNames.contains(nextRequiredParamName)) { + if (!unqualifiedNames.contains(nextRequiredParamName)) { + retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE); + } + } + } } - return param; + + return retVal; } @Override @@ -264,19 +288,18 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { return false; } - private List processWhitelistAndBlacklist(List theQualifiedNames, Set theQualifierWhitelist, Set theQualifierBlacklist) { + + private boolean passesWhitelistAndBlacklist(List theQualifiedNames, Set theQualifierWhitelist, Set theQualifierBlacklist) { if (theQualifierWhitelist == null && theQualifierBlacklist == null) { - return theQualifiedNames; + return true; } - ArrayList retVal = new ArrayList<>(theQualifiedNames.size()); for (String next : theQualifiedNames) { - QualifierDetails qualifiers = extractQualifiersFromParameterName(next); + QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(next); if (!qualifiers.passes(theQualifierWhitelist, theQualifierBlacklist)) { - continue; + return false; } - retVal.add(next); } - return retVal; + return true; } @Override @@ -284,52 +307,5 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { return getMethod().toString(); } - public static QualifierDetails extractQualifiersFromParameterName(String theParamName) { - QualifierDetails retVal = new QualifierDetails(); - if (theParamName == null || theParamName.length() == 0) { - return retVal; - } - - int dotIdx = -1; - int colonIdx = -1; - for (int idx = 0; idx < theParamName.length(); idx++) { - char nextChar = theParamName.charAt(idx); - if (nextChar == '.' && dotIdx == -1) { - dotIdx = idx; - } else if (nextChar == ':' && colonIdx == -1) { - colonIdx = idx; - } - } - - if (dotIdx != -1 && colonIdx != -1) { - if (dotIdx < colonIdx) { - retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx)); - retVal.setColonQualifier(theParamName.substring(colonIdx)); - retVal.setParamName(theParamName.substring(0, dotIdx)); - retVal.setWholeQualifier(theParamName.substring(dotIdx)); - } else { - retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx)); - retVal.setDotQualifier(theParamName.substring(dotIdx)); - retVal.setParamName(theParamName.substring(0, colonIdx)); - retVal.setWholeQualifier(theParamName.substring(colonIdx)); - } - } else if (dotIdx != -1) { - retVal.setDotQualifier(theParamName.substring(dotIdx)); - retVal.setParamName(theParamName.substring(0, dotIdx)); - retVal.setWholeQualifier(theParamName.substring(dotIdx)); - } else if (colonIdx != -1) { - retVal.setColonQualifier(theParamName.substring(colonIdx)); - retVal.setParamName(theParamName.substring(0, colonIdx)); - retVal.setWholeQualifier(theParamName.substring(colonIdx)); - } else { - retVal.setParamName(theParamName); - retVal.setColonQualifier(null); - retVal.setDotQualifier(null); - retVal.setWholeQualifier(null); - } - - return retVal; - } - } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchParameter.java index 550fd5f15de..269f21b3047 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchParameter.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchParameter.java @@ -42,8 +42,8 @@ import ca.uhn.fhir.util.ReflectionUtil; public class SearchParameter extends BaseQueryParameter { private static final String EMPTY_STRING = ""; - private static HashMap> ourParamQualifiers; - private static HashMap, RestSearchParameterTypeEnum> ourParamTypes; + private static final HashMap> ourParamQualifiers; + private static final HashMap, RestSearchParameterTypeEnum> ourParamTypes; static final String QUALIFIER_ANY_TYPE = ":*"; static { @@ -114,6 +114,7 @@ public class SearchParameter extends BaseQueryParameter { private Set myQualifierWhitelist; private boolean myRequired; private Class myType; + private boolean mySupportsRepetition = false; public SearchParameter() { } @@ -202,17 +203,17 @@ public class SearchParameter extends BaseQueryParameter { return myParamBinder.parse(theContext, getName(), theString); } - public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) { + public void setChainLists(String[] theChainWhitelist, String[] theChainBlacklist) { myQualifierWhitelist = new HashSet<>(theChainWhitelist.length); myQualifierWhitelist.add(QUALIFIER_ANY_TYPE); - for (int i = 0; i < theChainWhitelist.length; i++) { - if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) { + for (String nextChain : theChainWhitelist) { + if (nextChain.equals(OptionalParam.ALLOW_CHAIN_ANY)) { myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY); - } else if (theChainWhitelist[i].equals(EMPTY_STRING)) { + } else if (nextChain.equals(EMPTY_STRING)) { myQualifierWhitelist.add("."); } else { - myQualifierWhitelist.add('.' + theChainWhitelist[i]); + myQualifierWhitelist.add('.' + nextChain); } } @@ -248,42 +249,48 @@ public class SearchParameter extends BaseQueryParameter { this.myRequired = required; } + @Override + protected boolean supportsRepetition() { + return mySupportsRepetition; + } + @SuppressWarnings("unchecked") - public void setType(FhirContext theContext, final Class type, Class> theInnerCollectionType, Class> theOuterCollectionType) { + public void setType(FhirContext theContext, final Class theType, Class> theInnerCollectionType, Class> theOuterCollectionType) { - this.myType = type; - if (IQueryParameterType.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterTypeBinder((Class) type, myCompositeTypes); - } else if (IQueryParameterOr.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterOrBinder((Class>) type, myCompositeTypes); - } else if (IQueryParameterAnd.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterAndBinder((Class>) type, myCompositeTypes); - } else if (String.class.equals(type)) { + this.myType = theType; + if (IQueryParameterType.class.isAssignableFrom(theType)) { + myParamBinder = new QueryParameterTypeBinder((Class) theType, myCompositeTypes); + } else if (IQueryParameterOr.class.isAssignableFrom(theType)) { + myParamBinder = new QueryParameterOrBinder((Class>) theType, myCompositeTypes); + } else if (IQueryParameterAnd.class.isAssignableFrom(theType)) { + myParamBinder = new QueryParameterAndBinder((Class>) theType, myCompositeTypes); + mySupportsRepetition = true; + } else if (String.class.equals(theType)) { myParamBinder = new StringBinder(); myParamType = RestSearchParameterTypeEnum.STRING; - } else if (Date.class.equals(type)) { + } else if (Date.class.equals(theType)) { myParamBinder = new DateBinder(); myParamType = RestSearchParameterTypeEnum.DATE; - } else if (Calendar.class.equals(type)) { + } else if (Calendar.class.equals(theType)) { myParamBinder = new CalendarBinder(); myParamType = RestSearchParameterTypeEnum.DATE; - } else if (IPrimitiveType.class.isAssignableFrom(type) && ReflectionUtil.isInstantiable(type)) { - RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class>) type); + } else if (IPrimitiveType.class.isAssignableFrom(theType) && ReflectionUtil.isInstantiable(theType)) { + RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class>) theType); if (def.getNativeType() != null) { if (def.getNativeType().equals(Date.class)) { - myParamBinder = new FhirPrimitiveBinder((Class>) type); + myParamBinder = new FhirPrimitiveBinder((Class>) theType); myParamType = RestSearchParameterTypeEnum.DATE; } else if (def.getNativeType().equals(String.class)) { - myParamBinder = new FhirPrimitiveBinder((Class>) type); + myParamBinder = new FhirPrimitiveBinder((Class>) theType); myParamType = RestSearchParameterTypeEnum.STRING; } } } else { - throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName()); + throw new ConfigurationException("Unsupported data type for parameter: " + theType.getCanonicalName()); } - RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(type); + RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(theType); if (typeEnum != null) { Set builtInQualifiers = ourParamQualifiers.get(typeEnum); if (builtInQualifiers != null) { @@ -304,22 +311,22 @@ public class SearchParameter extends BaseQueryParameter { if (myParamType != null) { // ok - } else if (StringDt.class.isAssignableFrom(type)) { + } else if (StringDt.class.isAssignableFrom(theType)) { myParamType = RestSearchParameterTypeEnum.STRING; - } else if (BaseIdentifierDt.class.isAssignableFrom(type)) { + } else if (BaseIdentifierDt.class.isAssignableFrom(theType)) { myParamType = RestSearchParameterTypeEnum.TOKEN; - } else if (BaseQuantityDt.class.isAssignableFrom(type)) { + } else if (BaseQuantityDt.class.isAssignableFrom(theType)) { myParamType = RestSearchParameterTypeEnum.QUANTITY; - } else if (ReferenceParam.class.isAssignableFrom(type)) { + } else if (ReferenceParam.class.isAssignableFrom(theType)) { myParamType = RestSearchParameterTypeEnum.REFERENCE; - } else if (HasParam.class.isAssignableFrom(type)) { + } else if (HasParam.class.isAssignableFrom(theType)) { myParamType = RestSearchParameterTypeEnum.STRING; } else { - throw new ConfigurationException("Unknown search parameter type: " + type); + throw new ConfigurationException("Unknown search parameter theType: " + theType); } // NB: Once this is enabled, we should return true from handlesMissing if - // it's a collection type + // it's a collection theType // if (theInnerCollectionType != null) { // this.parser = new CollectionBinder(this.parser, theInnerCollectionType); // } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java index 238ba0af398..7aca1821a09 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java @@ -22,17 +22,13 @@ package ca.uhn.fhir.rest.server.method; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.lang.reflect.Method; -import java.util.IdentityHashMap; import java.util.List; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.TransactionParam; @@ -92,17 +88,17 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding } @Override - public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { + public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { if (theRequest.getRequestType() != RequestTypeEnum.POST) { - return false; + return MethodMatchEnum.NONE; } if (isNotBlank(theRequest.getOperation())) { - return false; + return MethodMatchEnum.NONE; } if (isNotBlank(theRequest.getResourceName())) { - return false; + return MethodMatchEnum.NONE; } - return true; + return MethodMatchEnum.EXACT; } @SuppressWarnings("unchecked") diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java index 2780e3c2da5..8b8d5097de7 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java @@ -31,7 +31,11 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.server.*; +import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; @@ -48,7 +52,13 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -119,8 +129,8 @@ public class HashMapResourceProvider implements IResour } @Create - public MethodOutcome create(@ResourceParam T theResource) { - createInternal(theResource); + public MethodOutcome create(@ResourceParam T theResource, RequestDetails theRequestDetails) { + createInternal(theResource, theRequestDetails); myCreateCount.incrementAndGet(); @@ -130,17 +140,17 @@ public class HashMapResourceProvider implements IResour .setId(theResource.getIdElement()); } - private void createInternal(@ResourceParam T theResource) { + private void createInternal(@ResourceParam T theResource, RequestDetails theRequestDetails) { long idPart = myNextId++; String idPartAsString = Long.toString(idPart); Long versionIdPart = 1L; - IIdType id = store(theResource, idPartAsString, versionIdPart); + IIdType id = store(theResource, idPartAsString, versionIdPart, theRequestDetails); theResource.setId(id); } @Delete - public MethodOutcome delete(@IdParam IIdType theId) { + public MethodOutcome delete(@IdParam IIdType theId, RequestDetails theRequestDetails) { TreeMap versions = myIdToVersionToResourceMap.get(theId.getIdPart()); if (versions == null || versions.isEmpty()) { throw new ResourceNotFoundException(theId); @@ -148,7 +158,7 @@ public class HashMapResourceProvider implements IResour long nextVersion = versions.lastEntry().getKey() + 1L; - IIdType id = store(null, theId.getIdPart(), nextVersion); + IIdType id = store(null, theId.getIdPart(), nextVersion, theRequestDetails); myDeleteCount.incrementAndGet(); @@ -310,7 +320,7 @@ public class HashMapResourceProvider implements IResour return fireInterceptorsAndFilterAsNeeded(retVal, theRequestDetails); } - private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) { + private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart, RequestDetails theRequestDetails) { IIdType id = myFhirContext.getVersion().newIdType(); String versionIdPart = Long.toString(theVersionIdPart); id.setParts(null, myResourceName, theIdPart, versionIdPart); @@ -348,6 +358,35 @@ public class HashMapResourceProvider implements IResour TreeMap versionToResource = getVersionToResource(theIdPart); versionToResource.put(theVersionIdPart, theResource); + if (theRequestDetails != null) { + IInterceptorBroadcaster interceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster(); + + if (theResource != null) { + if (!myIdToHistory.containsKey(theIdPart)) { + + // Interceptor call: STORAGE_PRESTORAGE_RESOURCE_CREATED + HookParams params = new HookParams() + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) + .add(IBaseResource.class, theResource); + interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, params); + interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, params); + + } else { + + // Interceptor call: STORAGE_PRESTORAGE_RESOURCE_UPDATED + HookParams params = new HookParams() + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) + .add(IBaseResource.class, myIdToHistory.get(theIdPart).getFirst()) + .add(IBaseResource.class, theResource); + interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, params); + interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, params); + + } + } + } + // Store to type history map myTypeHistory.addFirst(theResource); @@ -365,11 +404,12 @@ public class HashMapResourceProvider implements IResour @Update public MethodOutcome update( @ResourceParam T theResource, - @ConditionalUrlParam String theConditional) { + @ConditionalUrlParam String theConditional, + RequestDetails theRequestDetails) { ValidateUtil.isTrueOrThrowInvalidRequest(isBlank(theConditional), "This server doesn't support conditional update"); - boolean created = updateInternal(theResource); + boolean created = updateInternal(theResource, theRequestDetails); myUpdateCount.incrementAndGet(); return new MethodOutcome() @@ -378,7 +418,7 @@ public class HashMapResourceProvider implements IResour .setId(theResource.getIdElement()); } - private boolean updateInternal(@ResourceParam T theResource) { + private boolean updateInternal(@ResourceParam T theResource, RequestDetails theRequestDetails) { String idPartAsString = theResource.getIdElement().getIdPart(); TreeMap versionToResource = getVersionToResource(idPartAsString); @@ -392,7 +432,7 @@ public class HashMapResourceProvider implements IResour created = false; } - IIdType id = store(theResource, idPartAsString, versionIdPart); + IIdType id = store(theResource, idPartAsString, versionIdPart, theRequestDetails); theResource.setId(id); return created; } @@ -411,9 +451,9 @@ public class HashMapResourceProvider implements IResour */ public IIdType store(T theResource) { if (theResource.getIdElement().hasIdPart()) { - updateInternal(theResource); + updateInternal(theResource, null); } else { - createInternal(theResource); + createInternal(theResource, null); } return theResource.getIdElement(); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java index 2dfba738997..1558224d7d6 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java @@ -33,7 +33,16 @@ import static org.apache.commons.lang3.StringUtils.defaultIfBlank; /** * This class is a tenant identification strategy which assumes that a single path - * element will be present between the server base URL and the beginning + * element will be present between the server base URL and individual request. + *

        + * For example, + * with this strategy enabled, given the following URL on a server with base URL http://example.com/base, + * the server will extract the TENANT-A portion of the URL and use it as the tenant identifier. The + * request will then proceed to read the resource with ID Patient/123. + *

        + *

        + * GET http://example.com/base/TENANT-A/Patient/123 + *

        */ public class UrlBaseTenantIdentificationStrategy implements ITenantIdentificationStrategy { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/ITestingUiClientFactory.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/ITestingUiClientFactory.java index 544325e01fe..bddd1ccefd5 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/ITestingUiClientFactory.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/util/ITestingUiClientFactory.java @@ -27,7 +27,7 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; /** * This interface isn't used by hapi-fhir-base, but is used by the - * Web Testing UI + * Web Testing UI */ public interface ITestingUiClientFactory { diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java new file mode 100644 index 00000000000..8f5c2f3fc47 --- /dev/null +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java @@ -0,0 +1,101 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +public class ApacheProxyAddressStrategyTest { + + @Test + public void testWithoutForwarded() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://localhost/imagingstudy/fhir", serverBase); + } + + @Test + public void testWithForwardedHostWithoutForwardedProtoHttps() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + request.addHeader("X-Forwarded-Host", "my.example.host"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://my.example.host/imagingstudy/fhir", serverBase); + } + + @Test + public void testWithForwardedHostWithoutForwardedProtoHttp() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + false); + MockHttpServletRequest request = prepareRequest(); + request.addHeader("X-Forwarded-Host", "my.example.host"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("http://my.example.host/imagingstudy/fhir", serverBase); + } + + @Test + public void testWithForwarded() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + request.addHeader("X-Forwarded-Host", "my.example.host"); + request.addHeader("X-Forwarded-Proto", "https"); + request.addHeader("X-Forwarded-Prefix", "server-prefix/fhir"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://my.example.host/server-prefix/fhir", serverBase); + } + + @Test + public void testWithForwardedWithHostPrefixWithSlash() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + request.addHeader("host", "localhost"); + + request.addHeader("X-Forwarded-Host", "my.example.host"); + request.addHeader("X-Forwarded-Proto", "https"); + request.addHeader("X-Forwarded-Prefix", "/server-prefix/fhir"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://my.example.host/server-prefix/fhir", serverBase); + } + + @Test + public void testWithForwardedWithoutPrefix() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + + request.addHeader("X-Forwarded-Host", "my.example.host"); + request.addHeader("X-Forwarded-Proto", "https"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://my.example.host/imagingstudy/fhir", serverBase); + } + + @Test + public void testWithForwardedHostAndPort() { + ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy( + true); + MockHttpServletRequest request = prepareRequest(); + + request.addHeader("X-Forwarded-Host", "my.example.host"); + request.addHeader("X-Forwarded-Port", "345"); + String serverBase = addressStrategy.determineServerBase(null, request); + assertEquals("https://my.example.host:345/imagingstudy/fhir", + serverBase); + } + + private MockHttpServletRequest prepareRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setScheme("https"); + request.setServerPort(443); + request.setServletPath("/fhir"); + request.setServerName("localhost"); + request.setRequestURI("/imagingstudy/fhir/imagingstudy?_format=json"); + request.setContextPath("/imagingstudy"); + return request; + } +} \ No newline at end of file diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/CommonResourceSupertypeScannerTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/CommonResourceSupertypeScannerTest.java index 6e28a077c77..5d4016611d7 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/CommonResourceSupertypeScannerTest.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/CommonResourceSupertypeScannerTest.java @@ -3,14 +3,16 @@ package ca.uhn.fhir.rest.server; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.model.api.annotation.ResourceDef; -import java.util.List; -import static org.hamcrest.CoreMatchers.is; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import static org.junit.Assert.assertThat; import org.junit.Test; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public class CommonResourceSupertypeScannerTest { private final CommonResourceSupertypeScanner scanner = new CommonResourceSupertypeScanner(); diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/MethodMatchEnumTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/MethodMatchEnumTest.java new file mode 100644 index 00000000000..d234922418c --- /dev/null +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/MethodMatchEnumTest.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.rest.server.method; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class MethodMatchEnumTest { + + @Test + public void testOrder() { + assertEquals(0, MethodMatchEnum.NONE.ordinal()); + assertEquals(1, MethodMatchEnum.APPROXIMATE.ordinal()); + assertEquals(2, MethodMatchEnum.EXACT.ordinal()); + } + +} diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java index ba4ee6476ce..69aceeae851 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/ReadMethodBindingTest.java @@ -20,6 +20,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Method; import java.util.List; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -56,17 +57,17 @@ public class ReadMethodBindingTest { // Read ReadMethodBinding binding = createBinding(new MyProvider()); when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); - assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // VRead when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123")); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // Type history when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); when(myRequestDetails.getResourceName()).thenReturn("Patient"); when(myRequestDetails.getOperation()).thenReturn("_history"); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); } @@ -89,27 +90,27 @@ public class ReadMethodBindingTest { ReadMethodBinding binding = createBinding(new MyProvider()); when(myRequestDetails.getResourceName()).thenReturn("Observation"); when(myRequestDetails.getId()).thenReturn(new IdDt("Observation/123")); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // Read when(myRequestDetails.getResourceName()).thenReturn("Patient"); when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); - assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // VRead when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123")); when(myRequestDetails.getOperation()).thenReturn("_history"); - assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // Some other operation when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123")); when(myRequestDetails.getOperation()).thenReturn("$foo"); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); // History operation when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); when(myRequestDetails.getOperation()).thenReturn("_history"); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); } @@ -130,7 +131,7 @@ public class ReadMethodBindingTest { when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123")); ReadMethodBinding binding = createBinding(new MyProvider()); - assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails)); + assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails)); } public ReadMethodBinding createBinding(Object theProvider) throws NoSuchMethodException { diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java index 30c722bf5ed..e97f416e462 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java @@ -42,39 +42,39 @@ public class SearchMethodBindingTest { public void methodShouldNotMatchWhenUnderscoreQueryParameter() throws NoSuchMethodException { Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); } @Test public void methodShouldNotMatchWhenExtraQueryParameter() throws NoSuchMethodException { Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); } @Test public void methodMatchesOwnParams() throws NoSuchMethodException { Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}))), - Matchers.is(true)); + Matchers.is(MethodMatchEnum.EXACT)); Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "test", new String[]{"test"}))), - Matchers.is(true)); + Matchers.is(MethodMatchEnum.EXACT)); Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_test", new String[]{"test"}))), - Matchers.is(true)); + Matchers.is(MethodMatchEnum.EXACT)); } @Test @@ -83,10 +83,10 @@ public class SearchMethodBindingTest { ourLog.info("Testing binding: {}", binding); Assert.assertThat(binding.incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("refChainBlacklist.badChain", new String[]{"foo"}))), - Matchers.is(false)); + Matchers.is(MethodMatchEnum.NONE)); Assert.assertThat(binding.incomingServerRequestMatchesMethod( mockSearchRequest(ImmutableMap.of("refChainBlacklist.goodChain", new String[]{"foo"}))), - Matchers.is(true)); + Matchers.is(MethodMatchEnum.EXACT)); } private SearchMethodBinding getBinding(String name, Class... parameters) throws NoSuchMethodException { diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 834ad669a7d..8646bb73cfc 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java index 82c286be823..3ee48e6052c 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -26,10 +26,14 @@ import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.provider.BaseJpaProvider; import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; import ca.uhn.fhir.rest.client.api.IClientInterceptor; @@ -58,6 +62,7 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.CollectionUtils; @@ -161,6 +166,11 @@ public class FhirAutoConfiguration { @Configuration @EntityScan(basePackages = {"ca.uhn.fhir.jpa.entity", "ca.uhn.fhir.jpa.model.entity"}) + @Import({ + SubscriptionChannelConfig.class, + SubscriptionProcessorConfig.class, + SubscriptionSubmitterConfig.class + }) static class FhirJpaDaoConfiguration { @Bean @@ -171,6 +181,14 @@ public class FhirAutoConfiguration { return fhirDaoConfig; } + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.jpa") + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } + + @Bean @ConditionalOnMissingBean @ConfigurationProperties("hapi.fhir.jpa") diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java index 848ae31620b..4dea685810a 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.spring.boot.autoconfigure; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; import ca.uhn.fhir.rest.server.RestfulServer; diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index 31805755c96..1c199d2506e 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index 1d4b77cb9f0..50966b5c47a 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT hapi-fhir-spring-boot-sample-client-okhttp diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 09556aa2e9d..b3304dd7fdc 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT hapi-fhir-spring-boot-sample-server-jersey diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml index a087c508c21..4add751540a 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT hapi-fhir-spring-boot-sample-server-jpa diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 5b9868d5d9c..0170fa2b470 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT hapi-fhir-spring-boot-samples diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 4869dd92e80..8e61124271e 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 1b15296e40b..0a8e4593d00 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java index 6ed05ffa58a..731db1e8a57 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java @@ -60,7 +60,7 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; import ca.uhn.fhir.context.RuntimeResourceBlockDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.fhirpath.IFluentPath; import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IPrimitiveDatatype; diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 2e2a8777742..383348d5c43 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -74,6 +74,16 @@ commons-codec + + + com.google.code.gson + gson + true + + + + com.google.code.gson + gson + true + + xpp3 xpp3 diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java deleted file mode 100644 index 34c9d0f26c3..00000000000 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.apache.commons.io.Charsets; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderSimple; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class DefaultProfileValidationSupport implements IValidationSupport { - - private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/"; - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class); - - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); - - Set wantCodes = new HashSet<>(); - for (ConceptReferenceComponent next : theInclude.getConcept()) { - wantCodes.add(next.getCode()); - } - - CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); - if (system != null) { - List concepts = system.getConcept(); - addConcepts(theInclude, retVal, wantCodes, concepts); - } - - for (UriType next : theInclude.getValueSet()) { - ValueSet vs = myValueSets.get(defaultString(next.getValueAsString())); - if (vs != null) { - for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) { - ValueSetExpansionComponent contents = expandValueSet(theContext, nextInclude); - retVal.getContains().addAll(contents.getContains()); - } - } - } - - return retVal; - } - - private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set theWantCodes, List theConcepts) { - for (ConceptDefinitionComponent next : theConcepts) { - if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) { - theRetVal - .addContains() - .setSystem(theInclude.getSystem()) - .setCode(next.getCode()) - .setDisplay(next.getDisplay()); - } - addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept()); - } - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - retVal.addAll(myCodeSystems.values()); - retVal.addAll(myStructureDefinitions.values()); - retVal.addAll(myValueSets.values()); - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList(provideStructureDefinitionMap(theContext).values()); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); - } - - private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { - synchronized (this) { - Map codeSystems = myCodeSystems; - Map valueSets = myValueSets; - if (codeSystems == null || valueSets == null) { - codeSystems = new HashMap(); - valueSets = new HashMap(); - - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu3/model/valueset/valuesets.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu3/model/valueset/v2-tables.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu3/model/valueset/v3-codesystems.xml"); - - myCodeSystems = codeSystems; - myValueSets = valueSets; - } - - if (codeSystem) { - return codeSystems.get(theSystem); - } else { - return valueSets.get(theSystem); - } - } - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - Validate.notBlank(theUri, "theUri must not be null or blank"); - - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - - if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { - return (T) fetchValueSet(theContext, theUri); - } - - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { - String url = theUrl; - if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { - // no change - } else if (url.indexOf('/') == -1) { - url = URL_PREFIX_STRUCTURE_DEFINITION + url; - } else if (StringUtils.countMatches(url, '/') == 1) { - url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; - } - Map map = provideStructureDefinitionMap(theContext); - StructureDefinition retVal = map.get(url); - - if (retVal == null && url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { - String tryUrl = URL_PREFIX_STRUCTURE_DEFINITION + StringUtils.capitalize(url.substring(URL_PREFIX_STRUCTURE_DEFINITION.length())); - retVal = map.get(tryUrl); - } - - return retVal; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); - } - - public void flush() { - myCodeSystems = null; - myStructureDefinitions = null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - CodeSystem cs = fetchCodeSystem(theContext, theSystem); - return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; - } - - private void loadCodeSystems(FhirContext theContext, Map theCodeSystems, Map theValueSets, String theClasspath) { - ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); - InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - InputStreamReader reader = null; - if (inputStream != null) { - try { - reader = new InputStreamReader(inputStream, Charsets.UTF_8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof CodeSystem) { - CodeSystem nextValueSet = (CodeSystem) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextValueSet); - } - } else if (next.getResource() instanceof ValueSet) { - ValueSet nextValueSet = (ValueSet) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theValueSets.put(system, nextValueSet); - } - } - } - } finally { - IOUtils.closeQuietly(reader); - IOUtils.closeQuietly(inputStream); - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private void loadStructureDefinitions(FhirContext theContext, Map theCodeSystems, String theClasspath) { - ourLog.info("Loading structure definitions from classpath: {}", theClasspath); - InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - if (valuesetText != null) { - InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof StructureDefinition) { - StructureDefinition nextSd = (StructureDefinition) next.getResource(); - nextSd.getText().setDivAsString(""); - String system = nextSd.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextSd); - } - } - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private Map provideStructureDefinitionMap(FhirContext theContext) { - Map structureDefinitions = myStructureDefinitions; - if (structureDefinitions == null) { - structureDefinitions = new HashMap(); - - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu3/model/profile/profiles-resources.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu3/model/profile/profiles-types.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu3/model/profile/profiles-others.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu3/model/extension/extension-definitions.xml"); - - myStructureDefinitions = structureDefinitions; - } - return structureDefinitions; - } - - private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List conceptList, boolean theCaseSensitive) { - String code = theCode; - if (theCaseSensitive == false) { - code = code.toUpperCase(); - } - - return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code); - } - - private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List conceptList, boolean theCaseSensitive, String code) { - CodeValidationResult retVal = null; - for (ConceptDefinitionComponent next : conceptList) { - String nextCandidate = next.getCode(); - if (theCaseSensitive == false) { - nextCandidate = nextCandidate.toUpperCase(); - } - if (nextCandidate.equals(code)) { - retVal = new CodeValidationResult(null, null, next, next.getDisplay()); - break; - } - - // recurse - retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive); - if (retVal != null) { - break; - } - } - - if (retVal != null) { - retVal.setCodeSystemName(theCodeSystem.getName()); - retVal.setCodeSystemVersion(theCodeSystem.getVersion()); - } - - return retVal; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - if (isNotBlank(theValueSetUrl)) { - HapiWorkerContext workerContext = new HapiWorkerContext(theContext, this); - ValueSetExpander expander = new ValueSetExpanderSimple(workerContext, workerContext); - try { - ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl); - if (valueSet != null) { - ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null); - Optional haveMatch = expanded - .getValueset() - .getExpansion() - .getContains() - .stream() - .filter(t -> (Constants.codeSystemNotNeeded(theCodeSystem) || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode)) - .findFirst(); - if (haveMatch.isPresent()) { - return new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(theCode))); - } - } - } catch (Exception e) { - return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage()); - } - - return null; - } - - if (theCodeSystem != null) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return validateCode(theContext, theSystem, theCode, null, (String)null).asLookupCodeResult(theSystem, theCode); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; - } - -} diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java index 6dc6d3b74df..300f3410a86 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java @@ -24,14 +24,13 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; import ca.uhn.fhir.util.ReflectionUtil; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu3.hapi.fluentpath.FluentPathDstu3; +import org.hl7.fhir.dstu3.hapi.fluentpath.FhirPathDstu3; import org.hl7.fhir.dstu3.hapi.rest.server.Dstu3BundleFactory; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.*; @@ -45,18 +44,8 @@ public class FhirDstu3 implements IFhirVersion { private String myId; @Override - public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) { - return new FluentPathDstu3(theFhirContext); - } - - @Override - public IContextValidationSupport createValidationSupport() { - String className = "org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport"; - try { - return (IContextValidationSupport) Class.forName(className).newInstance(); - } catch (Exception theE) { - throw new ConfigurationException(className + " is not on classpath. Make sure that hapi-fhir-validation-VERSION.jar is available."); - } + public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) { + return new FhirPathDstu3(theFhirContext); } @Override diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/HapiWorkerContext.java index 84e62466d92..d87d808f894 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/HapiWorkerContext.java @@ -1,9 +1,9 @@ package org.hl7.fhir.dstu3.hapi.ctx; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.CoverageIgnore; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -17,23 +17,30 @@ import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderFactory; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderSimple; import org.hl7.fhir.dstu3.utils.INarrativeGenerator; import org.hl7.fhir.dstu3.utils.IResourceValidator; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.TerminologyServiceException; +import org.hl7.fhir.utilities.i18n.I18nBase; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory { +public final class HapiWorkerContext extends I18nBase implements IWorkerContext { + private static final Logger ourLog = LoggerFactory.getLogger(HapiWorkerContext.class); private final FhirContext myCtx; private final Cache myFetchedResourceCache; - private IValidationSupport myValidationSupport; private ExpansionProfile myExpansionProfile; @@ -48,6 +55,9 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); } myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build(); + + // Set a default locale + setValidationMessageLanguage(getLocale()); } @Override @@ -58,38 +68,22 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public List allStructures() { - return myValidationSupport.fetchAllStructureDefinitions(myCtx); + return myValidationSupport.fetchAllStructureDefinitions(); } @Override - public ValueSetExpansionOutcome expand(ValueSet theSource, ExpansionProfile theProfile) { - ValueSetExpansionOutcome vso; - try { - vso = getExpander().expand(theSource, theProfile); - } catch (InvalidRequestException e) { - throw e; - } catch (TerminologyServiceException e) { - throw new InvalidRequestException(e.getMessage(), e); - } catch (Exception e) { - throw new InternalErrorException(e); - } - if (vso.getError() != null) { - throw new InvalidRequestException(vso.getError()); + public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHierarchical) { + ValueSet input = new ValueSet(); + input.getCompose().addInclude(theInc); + IValidationSupport.ValueSetExpansionOutcome output = myValidationSupport.expandValueSet(myValidationSupport, null, input); + ValueSet outputValueSet = (ValueSet) output.getValueSet(); + if (outputValueSet != null) { + return outputValueSet.getExpansion(); } else { - return vso; + return null; } } - @Override - public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHeiarchical) { - return myValidationSupport.expandValueSet(myCtx, theInc); - } - @Override public StructureDefinition fetchTypeDefinition(String theCode) { return fetchResource(org.hl7.fhir.dstu3.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + theCode); @@ -100,7 +94,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return null; } else { - return myValidationSupport.fetchCodeSystem(myCtx, theSystem); + return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem); } } @@ -110,18 +104,18 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return null; } else { - try { - //noinspection unchecked - return (T) myFetchedResourceCache.get(theUri, t -> { - T resource = myValidationSupport.fetchResource(myCtx, theClass, theUri); - if (resource == null) { - throw new IllegalArgumentException(); - } - return resource; - }); - } catch (IllegalArgumentException e) { - return null; - } + try { + //noinspection unchecked + return (T) myFetchedResourceCache.get(theUri, t -> { + T resource = myValidationSupport.fetchResource(theClass, theUri); + if (resource == null) { + throw new IllegalArgumentException(); + } + return resource; + }); + } catch (IllegalArgumentException e) { + return null; + } } } @@ -140,15 +134,13 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public String getAbbreviation(String theName) { + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical) { throw new UnsupportedOperationException(); } @Override - public ValueSetExpander getExpander() { - ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this); - retVal.setMaxExpansionSize(Integer.MAX_VALUE); - return retVal; + public String getAbbreviation(String theName) { + throw new UnsupportedOperationException(); } @Override @@ -183,7 +175,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public List getResourceNames() { - List result = new ArrayList(); + List result = new ArrayList<>(); for (ResourceType next : ResourceType.values()) { result.add(next.name()); } @@ -247,13 +239,13 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return false; } else { - return myValidationSupport.isCodeSystemSupported(myCtx, theSystem); + return myValidationSupport.isCodeSystemSupported(myValidationSupport, theSystem); } } @Override public Set typeTails() { - return new HashSet(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code", + return new HashSet<>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code", "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint", "Timing", "Reference", "Annotation", "Signature", "Meta")); } @@ -280,11 +272,26 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) { - IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, (String)null); + ValidationOptions options = new ValidationOptions(); + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(options), theSystem, theCode, theDisplay, null); if (result == null) { return null; } - return new ValidationResult((IssueSeverity)result.getSeverity(), result.getMessage(), (ConceptDefinitionComponent)result.asConceptDefinition()); + + IssueSeverity severity = null; + if (result.getSeverity() != null) { + severity = IssueSeverity.fromCode(result.getSeverityCode()); + } + ConceptDefinitionComponent definition = new ConceptDefinitionComponent().setCode(result.getCode()); + return new ValidationResult(severity, result.getMessage(), definition); + } + + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; } @Override @@ -295,31 +302,12 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ValueSet theVs) { - /* - * The following valueset is a special case, since the BCP codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } - - /* - * The following valueset is a special case, since the mime types codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } - IValidationSupport.CodeValidationResult outcome; + ValidationOptions options = new ValidationOptions(); if (isNotBlank(theVs.getUrl())) { - outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl()); + outcome = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(options), theSystem, theCode, theDisplay, theVs.getUrl()); } else { - outcome = myValidationSupport.validateCodeInValueSet(myCtx, theSystem, theCode, theDisplay, theVs); + outcome = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(options), theSystem, theCode, theDisplay, theVs); } if (outcome != null && outcome.isOk()) { diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java deleted file mode 100644 index a63581a4b6c..00000000000 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.util.List; - -public interface IValidationSupport - extends ca.uhn.fhir.context.support.IContextValidationSupport { - - /** - * Expands the given portion of a ValueSet - * - * @param theInclude The portion to include - * @return The expansion - */ - @Override - ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude); - - /** - * Load and return all possible structure definitions - */ - @Override - List fetchAllStructureDefinitions(FhirContext theContext); - - /** - * Fetch a code system by Uri - * - * @param uri Canonical Uri of the code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String uri); - - /** - * Fetch a valueset by Uri - * - * @param uri Canonical Uri of the ValueSet - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - ValueSet fetchValueSet(FhirContext theContext, String uri); - - @Override - StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl); - - /** - * Returns true if codes in the given code system can be expanded - * or validated - * - * @param theSystem The URI for the code system, e.g. "http://loinc.org" - * @return Returns true if codes in the given code system can be - * validated - */ - @Override - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); - - /** - * Generate a snapshot from the given differential profile. - * - * @return Returns null if this module does not know how to handle this request - */ - StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName); - - - -} diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java similarity index 58% rename from hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java rename to hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java index cfb1daeaaa5..84bbca753dc 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java @@ -1,10 +1,10 @@ package org.hl7.fhir.dstu3.hapi.fluentpath; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.fluentpath.FluentPathExecutionException; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.fhirpath.FhirPathExecutionException; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.Base; import org.hl7.fhir.dstu3.utils.FHIRPathEngine; import org.hl7.fhir.exceptions.FHIRException; @@ -13,15 +13,12 @@ import org.hl7.fhir.instance.model.api.IBase; import java.util.List; import java.util.Optional; -public class FluentPathDstu3 implements IFluentPath { +public class FhirPathDstu3 implements IFhirPath { private FHIRPathEngine myEngine; - public FluentPathDstu3(FhirContext theCtx) { - if (!(theCtx.getValidationSupport() instanceof IValidationSupport)) { - throw new IllegalStateException("Validation support module configured on context appears to be for the wrong FHIR version- Does not extend " + IValidationSupport.class.getName()); - } - IValidationSupport validationSupport = (IValidationSupport) theCtx.getValidationSupport(); + public FhirPathDstu3(FhirContext theCtx) { + IValidationSupport validationSupport = theCtx.getValidationSupport(); myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport)); } @@ -32,12 +29,12 @@ public class FluentPathDstu3 implements IFluentPath { try { result = myEngine.evaluate((Base)theInput, thePath); } catch (FHIRException e) { - throw new FluentPathExecutionException(e); + throw new FhirPathExecutionException(e); } for (Base next : result) { if (!theReturnType.isAssignableFrom(next.getClass())) { - throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); + throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); } } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 3b58f877a14..8a1e1a9082b 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -58,6 +58,24 @@ public class JsonParserDstu3Test { ourCtx.setNarrativeGenerator(null); } + @Test + public void testEncodedResourceWithIncorrectRepresentationOfDecimalTypeToJson() { + DecimalType decimalType = new DecimalType(); + decimalType.setValueAsString(".5"); + MedicationRequest mr = new MedicationRequest(); + Dosage dosage = new Dosage(); + dosage.setDose(new SimpleQuantity() + .setValue(decimalType.getValue()) + .setUnit("{tablet}") + .setSystem("http://unitsofmeasure.org") + .setCode("{tablet}")); + mr.addDosageInstruction(dosage); + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); + assertEquals(BigDecimal.valueOf(0.5), mr.getDosageInstructionFirstRep().getDoseSimpleQuantity().getValue()); + assertTrue(encoded.contains("0.5")); + } /** * See #563 @@ -72,7 +90,8 @@ public class JsonParserDstu3Test { p.parseResource(input); fail(); } catch (DataFormatException e) { - assertEquals("Found incorrect type for element subject - Expected OBJECT and found SCALAR (STRING)", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Unexpected character ('=' (code 61)): was expecting a colon to separate field name and value\n" + + " at [Source: UNKNOWN; line: 4, column: 18]", e.getMessage()); } } @@ -488,32 +507,25 @@ public class JsonParserDstu3Test { String enc = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); ourLog.info(enc); - //@formatter:off - assertEquals("{\n" + - " \"resourceType\": \"Patient\",\n" + - " \"meta\": {\n" + - " \"security\": [\n" + - " {\n" + - " \"system\": \"SYSTEM1\",\n" + - " \"version\": \"VERSION1\",\n" + - " \"code\": \"CODE1\",\n" + - " \"display\": \"DISPLAY1\"\n" + - " },\n" + - " {\n" + - " \"system\": \"SYSTEM2\",\n" + - " \"version\": \"VERSION2\",\n" + - " \"code\": \"CODE2\",\n" + - " \"display\": \"DISPLAY2\"\n" + - " }\n" + - " ]\n" + - " },\n" + - " \"name\": [\n" + - " {\n" + - " \"family\": \"FAMILY\"\n" + - " }\n" + - " ]\n" + - "}", enc.trim()); - //@formatter:on + assertThat(enc.trim(), stringContainsInOrder("{", + " \"resourceType\": \"Patient\",", + " \"meta\": {", + " \"security\": [ {", + " \"system\": \"SYSTEM1\",", + " \"version\": \"VERSION1\",", + " \"code\": \"CODE1\",", + " \"display\": \"DISPLAY1\"", + " }, {", + " \"system\": \"SYSTEM2\",", + " \"version\": \"VERSION2\",", + " \"code\": \"CODE2\",", + " \"display\": \"DISPLAY2\"", + " } ]", + " },", + " \"name\": [ {", + " \"family\": \"FAMILY\"", + " } ]", + "}")); Patient parsed = ourCtx.newJsonParser().parseResource(Patient.class, enc); List gotLabels = parsed.getMeta().getSecurity(); @@ -1401,7 +1413,8 @@ public class JsonParserDstu3Test { String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}"; Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); - assertEquals("0.0000000000000001", ((Quantity) obs.getValue()).getValueElement().getValueAsString()); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.0000000000000001", valueElement.getValueAsString()); String str = ourCtx.newJsonParser().encodeResourceToString(obs); ourLog.info(str); @@ -1509,7 +1522,7 @@ public class JsonParserDstu3Test { assertEquals("foo", parsed.getValueDateTimeType().getValueAsString()); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); - verify(errorHandler, times(1)).invalidValue(isNull(), eq("foo"), msgCaptor.capture()); + verify(errorHandler, times(1)).invalidValue(any(), eq("foo"), msgCaptor.capture()); assertEquals("Invalid date/time format: \"foo\"", msgCaptor.getValue()); String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed); @@ -1541,8 +1554,8 @@ public class JsonParserDstu3Test { assertEquals(null, parsed.getGenderElement().getValueAsString()); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); - verify(errorHandler, times(1)).invalidValue(isNull(), eq(""), msgCaptor.capture()); - assertEquals("Attribute values must not be empty (\"\")", msgCaptor.getValue()); + verify(errorHandler, times(1)).invalidValue(any(), eq(""), msgCaptor.capture()); + assertEquals("Attribute value must not be empty (\"\")", msgCaptor.getValue()); String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed); assertEquals("{\"resourceType\":\"Patient\"}", encoded); @@ -1561,7 +1574,7 @@ public class JsonParserDstu3Test { assertEquals("foo", parsed.getGenderElement().getValueAsString()); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); - verify(errorHandler, times(1)).invalidValue(isNull(), eq("foo"), msgCaptor.capture()); + verify(errorHandler, times(1)).invalidValue(any(), eq("foo"), msgCaptor.capture()); assertEquals("Unknown AdministrativeGender code 'foo'", msgCaptor.getValue()); String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed); @@ -1993,13 +2006,13 @@ public class JsonParserDstu3Test { ourCtx.newJsonParser().parseResource("FOO"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: 'F' (must be '{')", e.getMessage()); } try { ourCtx.newJsonParser().parseResource("[\"aaa\"]"); fail(); } catch (DataFormatException e) { - assertEquals("Failed to parse JSON content, error was: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); + assertEquals("Failed to parse JSON encoded FHIR content: Content does not appear to be FHIR JSON, first non-whitespace character was: '[' (must be '{')", e.getMessage()); } assertEquals(Bundle.class, ourCtx.newJsonParser().parseResource(" {\"resourceType\" : \"Bundle\"}").getClass()); @@ -2221,25 +2234,47 @@ public class JsonParserDstu3Test { @Test public void testParseWithPrecision() { - String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}"; - Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); - DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); - assertEquals("0.000000000000000100", valueElement.getValueAsString()); +// BigDecimal d0 = new BigDecimal("0.1"); +// BigDecimal d1 = new BigDecimal("0.1000"); +// +// ourLog.info("Value: {}", d0); +// ourLog.info("Value: {}", d1); - String str = ourCtx.newJsonParser().encodeResourceToString(obs); - ourLog.info(str); - assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}", str); + { + String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0100}}"; + Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.0100", valueElement.getValueAsString()); + String str = ourCtx.newJsonParser().encodeResourceToString(obs); + ourLog.info(str); + assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0100}}", str); + } + { + String input = "{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}"; + Observation obs = ourCtx.newJsonParser().parseResource(Observation.class, input); + DecimalType valueElement = ((Quantity) obs.getValue()).getValueElement(); + assertEquals("0.000000000000000100", valueElement.getValueAsString()); + String str = ourCtx.newJsonParser().encodeResourceToString(obs); + ourLog.info(str); + assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.000000000000000100}}", str); + } } - @Test(expected = DataFormatException.class) + @Test public void testParseWithTrailingContent() { String bundle = "{\n" + - " \"resourceType\" : \"Bundle\",\n" + - " \"total\" : 1\n" + + " \"resourceType\": \"Bundle\",\n" + + " \"total\": 1\n" + "}}"; - ourCtx.newJsonParser().parseResource(Bundle.class, bundle); + try { + ourCtx.newJsonParser().parseResource(Bundle.class, bundle); + fail(); + } catch (DataFormatException e) { + assertEquals("Failed to parse JSON encoded FHIR content: Unexpected close marker '}': expected ']' (for root starting at [Source: UNKNOWN; line: 1, column: 0])\n" + + " at [Source: UNKNOWN; line: 4, column: 3]", e.getMessage()); + } } @Test diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 8023417d1de..16d90cfb763 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -45,6 +45,7 @@ import org.xmlunit.diff.ElementSelectors; import java.io.IOException; import java.io.StringReader; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; @@ -70,8 +71,27 @@ public class XmlParserDstu3Test { ourCtx.setNarrativeGenerator(null); } + @Test + public void testEncodedResourceWithIncorrectRepresentationOfDecimalTypeToXml() { + DecimalType decimalType = new DecimalType(); + decimalType.setValueAsString(".5"); + MedicationRequest mr = new MedicationRequest(); + Dosage dosage = new Dosage(); + dosage.setDose(new SimpleQuantity() + .setValue(decimalType.getValue()) + .setUnit("{tablet}") + .setSystem("http://unitsofmeasure.org") + .setCode("{tablet}")); + mr.addDosageInstruction(dosage); + String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newXmlParser().parseResource(MedicationRequest.class, encoded); + assertEquals(BigDecimal.valueOf(0.5), mr.getDosageInstructionFirstRep().getDoseSimpleQuantity().getValue()); + assertTrue(encoded.contains("0.5")); + } + /** - * We specifically include extensions on CapabilityStatment even in + * We specifically include extensions on CapabilityStatement even in * summary mode, since this is behaviour that people depend on */ @Test @@ -819,7 +839,6 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -834,7 +853,6 @@ public class XmlParserDstu3Test { "", "", "", - "", "", "", "", @@ -879,7 +897,6 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -892,7 +909,6 @@ public class XmlParserDstu3Test { "", "", "", - "", "", "", "", @@ -1542,7 +1558,7 @@ public class XmlParserDstu3Test { parser.encodeResourceToString(p); fail(); } catch (DataFormatException e) { - assertEquals("Extension contains both a value and nested extensions: Patient(res).extension", e.getMessage()); + assertEquals("[element=\"Patient(res).extension\"] Extension contains both a value and nested extensions", e.getMessage()); } } @@ -1915,7 +1931,7 @@ public class XmlParserDstu3Test { } @Test - public void testEncodeUndeclaredBlock() throws Exception { + public void testEncodeUndeclaredBlock() { FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent(); source.getMessageHeaderApplicationId().setValue("APPID"); source.setName("NAME"); @@ -1945,7 +1961,7 @@ public class XmlParserDstu3Test { Patient patient = new Patient(); patient.addAddress().setUse(AddressUse.HOME); EnumFactory fact = new AddressUseEnumFactory(); - PrimitiveType enumeration = new Enumeration(fact).setValue(AddressUse.HOME); + PrimitiveType enumeration = new Enumeration<>(fact).setValue(AddressUse.HOME); patient.addExtension().setUrl("urn:foo").setValue(enumeration); String val = parser.encodeResourceToString(patient); @@ -1961,7 +1977,7 @@ public class XmlParserDstu3Test { @Test public void testEncodeWithContained() { - List contained = new ArrayList(); + List contained = new ArrayList<>(); // Will be added by reference Patient p = new Patient(); @@ -2017,7 +2033,7 @@ public class XmlParserDstu3Test { } @Test - public void testEncodeWithDontEncodeElements() throws Exception { + public void testEncodeWithDontEncodeElements() { Patient patient = new Patient(); patient.setId("123"); patient.getMeta().addProfile("http://profile"); @@ -2072,7 +2088,7 @@ public class XmlParserDstu3Test { { IParser p = ourCtx.newXmlParser(); p.setDontEncodeElements(Sets.newHashSet("Patient.meta")); - p.setEncodeElements(new HashSet(Arrays.asList("Patient.name"))); + p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name"))); p.setPrettyPrint(true); String out = p.encodeResourceToString(patient); ourLog.info(out); @@ -2086,7 +2102,7 @@ public class XmlParserDstu3Test { } @Test - public void testEncodeWithEncodeElements() throws Exception { + public void testEncodeWithEncodeElements() { Patient patient = new Patient(); patient.getMeta().addProfile("http://profile"); patient.addName().setFamily("FAMILY"); @@ -2109,7 +2125,7 @@ public class XmlParserDstu3Test { } { IParser p = ourCtx.newXmlParser(); - p.setEncodeElements(new HashSet(Arrays.asList("Patient.name"))); + p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name"))); p.setPrettyPrint(true); String out = p.encodeResourceToString(bundle); ourLog.info(out); @@ -2120,7 +2136,7 @@ public class XmlParserDstu3Test { } { IParser p = ourCtx.newXmlParser(); - p.setEncodeElements(new HashSet(Arrays.asList("Patient"))); + p.setEncodeElements(new HashSet<>(Arrays.asList("Patient"))); p.setPrettyPrint(true); String out = p.encodeResourceToString(bundle); ourLog.info(out); @@ -2133,7 +2149,7 @@ public class XmlParserDstu3Test { } @Test - public void testEncodeWithEncodeElementsAppliesToChildResourcesOnly() throws Exception { + public void testEncodeWithEncodeElementsAppliesToChildResourcesOnly() { Patient patient = new Patient(); patient.getMeta().addProfile("http://profile"); patient.addName().setFamily("FAMILY"); @@ -2188,7 +2204,7 @@ public class XmlParserDstu3Test { } @Test - public void testMoreExtensions() throws Exception { + public void testMoreExtensions() { Patient patient = new Patient(); patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135"); @@ -2508,42 +2524,27 @@ public class XmlParserDstu3Test { output = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(pat); ourLog.info(output); - assertThat(output, stringContainsInOrder( - "{", + assertThat(output, stringContainsInOrder("{", " \"resourceType\": \"Patient\",", " \"id\": \"someid\",", " \"_id\": {", - " \"fhir_comments\": [", - " \" comment 1 \"", - " ]", + " \"fhir_comments\": [ \" comment 1 \" ]", " },", - " \"extension\": [", - " {", - " \"fhir_comments\": [", - " \" comment 2 \",", - " \" comment 7 \"", - " ],", - " \"url\": \"urn:patientext:att\",", - " \"valueAttachment\": {", - " \"fhir_comments\": [", - " \" comment 3 \",", - " \" comment 6 \"", - " ],", - " \"contentType\": \"aaaa\",", - " \"_contentType\": {", - " \"fhir_comments\": [", - " \" comment 4 \"", - " ]", - " },", - " \"data\": \"AAAA\",", - " \"_data\": {", - " \"fhir_comments\": [", - " \" comment 5 \"", - " ]", - " }", + " \"extension\": [ {", + " \"fhir_comments\": [ \" comment 2 \", \" comment 7 \" ],", + " \"url\": \"urn:patientext:att\",", + " \"valueAttachment\": {", + " \"fhir_comments\": [ \" comment 3 \", \" comment 6 \" ],", + " \"contentType\": \"aaaa\",", + " \"_contentType\": {", + " \"fhir_comments\": [ \" comment 4 \" ]", + " },", + " \"data\": \"AAAA\",", + " \"_data\": {", + " \"fhir_comments\": [ \" comment 5 \" ]", " }", " }", - " ]", + " } ]", "}")); } @@ -3134,7 +3135,7 @@ public class XmlParserDstu3Test { p.parseResource(resource); fail(); } catch (DataFormatException e) { - assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"1\": Invalid boolean string: '1'", e.getMessage()); + assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: [element=\"active\"] Invalid attribute value \"1\": Invalid boolean string: '1'", e.getMessage()); } LenientErrorHandler errorHandler = new LenientErrorHandler(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java index 082a737d845..d63d4dcb8d9 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserDstu3Test.java @@ -1,426 +1,404 @@ -package ca.uhn.fhir.parser.jsonlike; - -import java.io.IOException; -import java.io.StringReader; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; - -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Reference; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IJsonLikeParser; -import ca.uhn.fhir.parser.json.GsonStructure; -import ca.uhn.fhir.parser.json.JsonLikeStructure; -import ca.uhn.fhir.parser.json.JsonLikeWriter; -import ca.uhn.fhir.util.TestUtil; - -public class JsonLikeParserDstu3Test { - private static FhirContext ourCtx = FhirContext.forDstu3(); - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu3Test.class); - - /** - * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) - */ - @Test - public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception { - String content = IOUtils.toString(JsonLikeParserDstu3Test.class.getResourceAsStream("/bundle_with_woven_obs.xml")); - - Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, content); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); - ourLog.info(encoded); - - JsonLikeStructure jsonLikeStructure = new GsonStructure(); - jsonLikeStructure.load(new StringReader(encoded)); - - IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); - - Bundle bundle = jsonLikeparser.parseResource(Bundle.class, jsonLikeStructure); - - } - - /** - * Test JSON-Like writer using custom stream writer - * - */ - @Test - public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { - String refVal = "http://my.org/FooBar"; - - Patient fhirPat = new Patient(); - fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); - - IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); - JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); - - jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); - Map jsonLikeMap = jsonLikeWriter.getResultMap(); - - System.out.println("encoded map: " + jsonLikeMap.toString()); - - Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); - Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); - - Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); - Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); - - List extensions = (List)jsonLikeMap.get("extension"); - Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); - Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); - - Map extension = (Map)extensions.get(0); - Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); - Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); - Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); - - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - - - public static class JsonLikeMapWriter extends JsonLikeWriter { - - private Map target; - - private static class Block { - private BlockType type; - private String name; - private Map object; - private List array; - public Block(BlockType type) { - this.type = type; - } - public BlockType getType() { - return type; - } - public String getName() { - return name; - } - public void setName(String currentName) { - this.name = currentName; - } - public Map getObject() { - return object; - } - public void setObject(Map currentObject) { - this.object = currentObject; - } - public List getArray() { - return array; - } - public void setArray(List currentArray) { - this.array = currentArray; - } - } - private enum BlockType { - NONE, OBJECT, ARRAY - } - private Block currentBlock = new Block(BlockType.NONE); - private Stack blockStack = new Stack(); - - public JsonLikeMapWriter () { - super(); - } - - public Map getResultMap() { - return target; - } - public void setResultMap(Map target) { - this.target = target; - } - - @Override - public JsonLikeWriter init() throws IOException { - if (target != null) { - target.clear(); - } - currentBlock = new Block(BlockType.NONE); - blockStack.clear(); - return this; - } - - @Override - public JsonLikeWriter flush() throws IOException { - if (currentBlock.getType() != BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); - } - return this; - } - - @Override - public void close() { - // nothing to do - } - - @Override - public JsonLikeWriter beginObject() throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - Map newObject = null; - if (currentBlock.getType() == BlockType.NONE) { - if (null == target) { - // for this test, we don't care about ordering of map elements - // target = new EntryOrderedMap(); - target = new HashMap(); - } - newObject = target; - } else { - // for this test, we don't care about ordering of map elements - // newObject = new EntryOrderedMap(); - newObject = new HashMap(); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.OBJECT); - currentBlock.setObject(newObject); - return this; - } - - @Override - public JsonLikeWriter beginArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setArray(new ArrayList()); - return this; - } - - @Override - public JsonLikeWriter beginObject(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.OBJECT); - currentBlock.setName(name); - // for this test, we don't care about ordering of map elements - // currentBlock.setObject(new EntryOrderedMap()); - currentBlock.setObject(new HashMap()); - return this; - } - - @Override - public JsonLikeWriter beginArray(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setName(name); - currentBlock.setArray(new ArrayList()); - return this; - } - - @Override - public JsonLikeWriter write(String value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(BigInteger value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(BigDecimal value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(long value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Long.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(double value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Double.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(Boolean value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(value); - return this; - } - - @Override - public JsonLikeWriter write(boolean value) throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(Boolean.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter writeNull() throws IOException { - if (currentBlock.getType() == BlockType.OBJECT) { - throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); - } - currentBlock.getArray().add(null); - return this; - } - - @Override - public JsonLikeWriter write(String name, String value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, BigInteger value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - @Override - public JsonLikeWriter write(String name, BigDecimal value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, long value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Long.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(String name, double value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Double.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter write(String name, Boolean value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, value); - return this; - } - - @Override - public JsonLikeWriter write(String name, boolean value) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, Boolean.valueOf(value)); - return this; - } - - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, null); - return this; - } - - @Override - public JsonLikeWriter endObject() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); - } else { - if (currentBlock.getType() != BlockType.OBJECT) { - ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); - } - endBlock(); - } - return this; - } - - @Override - public JsonLikeWriter endArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); - } else { - if (currentBlock.getType() != BlockType.ARRAY) { - ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); - } - endBlock(); - } - return this; - } - - @Override - public JsonLikeWriter endBlock() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); - } else { - Object toPut = null; - if (currentBlock.getType() == BlockType.ARRAY) { - toPut = currentBlock.getArray(); - } else { - toPut = currentBlock.getObject(); - } - Block parentBlock = blockStack.pop(); - if (parentBlock.getType() == BlockType.OBJECT) { - parentBlock.getObject().put(currentBlock.getName(), toPut); - } else - if (parentBlock.getType() == BlockType.ARRAY) { - parentBlock.getArray().add(toPut); - } - currentBlock = parentBlock; - } - return this; - } - - } - -} +package ca.uhn.fhir.parser.jsonlike; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +public class JsonLikeParserDstu3Test { + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu3Test.class); + + /** + * Test for JSON Parser with user-supplied JSON-like structure (use default GSON) + */ + @Test + public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception { + String content = IOUtils.toString(JsonLikeParserDstu3Test.class.getResourceAsStream("/bundle_with_woven_obs.xml")); + + Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, content); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); + ourLog.info(encoded); + + JsonLikeStructure jsonLikeStructure = new JacksonStructure(); + jsonLikeStructure.load(new StringReader(encoded)); + + IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); + + Bundle bundle = jsonLikeparser.parseResource(Bundle.class, jsonLikeStructure); + + } + + /** + * Test JSON-Like writer using custom stream writer + * + */ + @Test + public void testJsonLikeParseWithCustomJSONStreamWriter() throws Exception { + String refVal = "http://my.org/FooBar"; + + Patient fhirPat = new Patient(); + fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); + + IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser(); + JsonLikeMapWriter jsonLikeWriter = new JsonLikeMapWriter(); + + jsonLikeParser.encodeResourceToJsonLikeWriter(fhirPat, jsonLikeWriter); + Map jsonLikeMap = jsonLikeWriter.getResultMap(); + + System.out.println("encoded map: " + jsonLikeMap.toString()); + + Assert.assertNotNull("Encoded resource missing 'resourceType' element", jsonLikeMap.get("resourceType")); + Assert.assertEquals("Expecting 'resourceType'='Patient'; found '"+jsonLikeMap.get("resourceType")+"'", jsonLikeMap.get("resourceType"), "Patient"); + + Assert.assertNotNull("Encoded resource missing 'extension' element", jsonLikeMap.get("extension")); + Assert.assertTrue("'extension' element is not a List", (jsonLikeMap.get("extension") instanceof List)); + + List extensions = (List)jsonLikeMap.get("extension"); + Assert.assertEquals("'extnesion' array has more than one entry", 1, extensions.size()); + Assert.assertTrue("'extension' array entry is not a Map", (extensions.get(0) instanceof Map)); + + Map extension = (Map)extensions.get(0); + Assert.assertNotNull("'extension' entry missing 'url' member", extension.get("url")); + Assert.assertTrue("'extension' entry 'url' member is not a String", (extension.get("url") instanceof String)); + Assert.assertEquals("Expecting '/extension[]/url' = 'x1'; found '"+extension.get("url")+"'", "x1", (String)extension.get("url")); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + + + public static class JsonLikeMapWriter extends JsonLikeWriter { + + private Map target; + + private static class Block { + private BlockType type; + private String name; + private Map object; + private List array; + public Block(BlockType type) { + this.type = type; + } + public BlockType getType() { + return type; + } + public String getName() { + return name; + } + public void setName(String currentName) { + this.name = currentName; + } + public Map getObject() { + return object; + } + public void setObject(Map currentObject) { + this.object = currentObject; + } + public List getArray() { + return array; + } + public void setArray(List currentArray) { + this.array = currentArray; + } + } + private enum BlockType { + NONE, OBJECT, ARRAY + } + private Block currentBlock = new Block(BlockType.NONE); + private Stack blockStack = new Stack(); + + public JsonLikeMapWriter () { + super(); + } + + public Map getResultMap() { + return target; + } + public void setResultMap(Map target) { + this.target = target; + } + + @Override + public JsonLikeWriter init() throws IOException { + if (target != null) { + target.clear(); + } + currentBlock = new Block(BlockType.NONE); + blockStack.clear(); + return this; + } + + @Override + public JsonLikeWriter flush() throws IOException { + if (currentBlock.getType() != BlockType.NONE) { + throw new IOException("JsonLikeStreamWriter.flush() called but JSON document is not finished"); + } + return this; + } + + @Override + public void close() { + // nothing to do + } + + @Override + public JsonLikeWriter beginObject() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + Map newObject = null; + if (currentBlock.getType() == BlockType.NONE) { + if (null == target) { + // for this test, we don't care about ordering of map elements + // target = new EntryOrderedMap(); + target = new HashMap(); + } + newObject = target; + } else { + // for this test, we don't care about ordering of map elements + // newObject = new EntryOrderedMap(); + newObject = new HashMap(); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setObject(newObject); + return this; + } + + @Override + public JsonLikeWriter beginObject(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.OBJECT); + currentBlock.setName(name); + // for this test, we don't care about ordering of map elements + // currentBlock.setObject(new EntryOrderedMap()); + currentBlock.setObject(new HashMap()); + return this; + } + + @Override + public JsonLikeWriter beginArray(String name) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + blockStack.push(currentBlock); + currentBlock = new Block(BlockType.ARRAY); + currentBlock.setName(name); + currentBlock.setArray(new ArrayList()); + return this; + } + + @Override + public JsonLikeWriter write(String value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(long value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(double value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(value); + return this; + } + + @Override + public JsonLikeWriter write(boolean value) throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter writeNull() throws IOException { + if (currentBlock.getType() == BlockType.OBJECT) { + throw new IOException("Unnamed JSON elements can only be created in JSON arrays"); + } + currentBlock.getArray().add(null); + return this; + } + + @Override + public JsonLikeWriter write(String name, String value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, BigInteger value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + @Override + public JsonLikeWriter write(String name, BigDecimal value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, long value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Long.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, double value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Double.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter write(String name, Boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, value); + return this; + } + + @Override + public JsonLikeWriter write(String name, boolean value) throws IOException { + if (currentBlock.getType() == BlockType.ARRAY) { + throw new IOException("Named JSON elements can only be created in JSON objects"); + } + currentBlock.getObject().put(name, Boolean.valueOf(value)); + return this; + } + + @Override + public JsonLikeWriter endObject() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endObject(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.OBJECT) { + ourLog.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endArray() throws IOException { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); + } else { + if (currentBlock.getType() != BlockType.ARRAY) { + ourLog.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)"); + } + endBlock(); + } + return this; + } + + @Override + public JsonLikeWriter endBlock() { + if (currentBlock.getType() == BlockType.NONE) { + ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); + } else { + Object toPut = null; + if (currentBlock.getType() == BlockType.ARRAY) { + toPut = currentBlock.getArray(); + } else { + toPut = currentBlock.getObject(); + } + Block parentBlock = blockStack.pop(); + if (parentBlock.getType() == BlockType.OBJECT) { + parentBlock.getObject().put(currentBlock.getName(), toPut); + } else + if (parentBlock.getType() == BlockType.ARRAY) { + parentBlock.getArray().add(toPut); + } + currentBlock = parentBlock; + } + return this; + } + + } + +} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/param/ReferenceParamTest.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/param/ReferenceParamTest.java index f727d380bf5..b40c76088ad 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/param/ReferenceParamTest.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/param/ReferenceParamTest.java @@ -11,6 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class ReferenceParamTest { @@ -202,6 +203,101 @@ public class ReferenceParamTest { } + @Test + public void testGetIdPartAsBigDecimal() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "123"); + + assertEquals("123", rp.getIdPartAsBigDecimal().toPlainString()); + } + + @Test + public void testGetIdPart() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "123"); + + assertTrue(rp.isIdPartValidLong()); + assertEquals("123", rp.getIdPart()); + assertEquals(null, rp.getResourceType(ourCtx)); + } + + @Test + public void testGetIdPartWithType() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, ":Patient", "123"); + + assertEquals("123", rp.getIdPart()); + assertEquals("Patient", rp.getResourceType(ourCtx).getSimpleName()); + } + + @Test + public void testSetValueWithType() { + ReferenceParam rp = new ReferenceParam(); + rp.setValue("Patient/123"); + + assertEquals("123", rp.getIdPart()); + assertEquals("Patient", rp.getResourceType(ourCtx).getSimpleName()); + } + + @Test + public void testSetValueWithoutType() { + ReferenceParam rp = new ReferenceParam(); + rp.setValue("123"); + + assertEquals("123", rp.getIdPart()); + assertEquals(null, rp.getResourceType(ourCtx)); + } + + @Test + public void testGetIdPartAsLong() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "123"); + + assertEquals(123L, rp.getIdPartAsLong().longValue()); + } + + @Test + public void testToStringParam() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "123"); + + assertEquals("123", rp.toStringParam(ourCtx).getValue()); + } + + @Test + public void testToTokenParam() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "123"); + + assertEquals("123", rp.toTokenParam(ourCtx).getValue()); + } + + @Test + public void testToDateParam() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "2020-10-01"); + + assertEquals("2020-10-01", rp.toDateParam(ourCtx).getValueAsString()); + } + + @Test + public void testToNumberParam() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "1.23"); + + assertEquals("1.23", rp.toNumberParam(ourCtx).getValue().toPlainString()); + } + + @Test + public void testToQuantityParam() { + ReferenceParam rp = new ReferenceParam(); + rp.setValueAsQueryToken(ourCtx, null, null, "1.23|http://unitsofmeasure.org|cm"); + + assertEquals("1.23", rp.toQuantityParam(ourCtx).getValue().toPlainString()); + assertEquals("http://unitsofmeasure.org", rp.toQuantityParam(ourCtx).getSystem()); + assertEquals("cm", rp.toQuantityParam(ourCtx).getUnits()); + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ReadDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ReadDstu3Test.java index daab5d2f35f..2177c4ddba6 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ReadDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ReadDstu3Test.java @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; @@ -64,6 +65,131 @@ public class ReadDstu3Test { "")); } + @Test + public void testInvalidQueryParamsInRead() throws Exception { + CloseableHttpResponse status; + HttpGet httpGet; + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_contained=both&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_containedType=contained&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_count=10&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_include=Patient:organization&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_revinclude=Provenance:target&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_sort=family&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2?_total=accurate&_format=xml&_pretty=true"); + status = ourClient.execute(httpGet); + try (InputStream inputStream = status.getEntity().getContent()) { + assertEquals(400, status.getStatusLine().getStatusCode()); + + String responseContent = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + assertThat(responseContent, stringContainsInOrder( + "", + " ", + " ", + " ", + " ", + " ", + "" + )); + } + } + @Test public void testIfModifiedSince() throws Exception { diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchCountParamDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchCountParamDstu3Test.java index 867fa36ed75..c62b872f10c 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchCountParamDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchCountParamDstu3Test.java @@ -112,7 +112,6 @@ public class SearchCountParamDstu3Test { assertEquals("searchWithNoCountParam", ourLastMethod); assertEquals(null, ourLastParam); - //@formatter:off assertThat(responseContent, stringContainsInOrder( "", "", @@ -122,7 +121,6 @@ public class SearchCountParamDstu3Test { "", "", "")); - //@formatter:on } finally { IOUtils.closeQuietly(status.getEntity().getContent()); diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index ab9eb4e3214..a7719f3e0dc 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -85,6 +85,16 @@ true + + + com.google.code.gson + gson + true + + org.xmlunit diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java index 28c8e4a7fe4..6ee07334272 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java @@ -24,13 +24,12 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Date; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.dstu2.model.*; import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.*; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; @@ -42,15 +41,10 @@ public class FhirDstu2Hl7Org implements IFhirVersion { private String myId; @Override - public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) { + public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) { throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts"); } - @Override - public IContextValidationSupport createValidationSupport() { - throw new UnsupportedOperationException("Validation support is not supported in DSTU2 contexts"); - } - @Override public StructureDefinition generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase) { StructureDefinition retVal = new StructureDefinition(); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java index e26ffe9f5cc..08615623b2a 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserHl7OrgDstu2Test.java @@ -1097,12 +1097,6 @@ public class JsonParserHl7OrgDstu2Test { assertEquals("idsystem", p.getIdentifier().get(0).getSystem()); } - @Test - public void testParseSingleQuotes() { - ourCtx.newJsonParser().parseResource(Bundle.class, "{ \"resourceType\": \"Bundle\" }"); - ourCtx.newJsonParser().parseResource(Bundle.class, "{ 'resourceType': 'Bundle' }"); - } - /** * HAPI FHIR < 0.6 incorrectly used "resource" instead of "reference" */ diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientServerValidationTestHl7OrgDstu2.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientServerValidationTestHl7OrgDstu2.java index 9a24569f3ab..392538e5343 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientServerValidationTestHl7OrgDstu2.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientServerValidationTestHl7OrgDstu2.java @@ -100,7 +100,7 @@ public class ClientServerValidationTestHl7OrgDstu2 { @Test public void testServerReturnsWrongVersionForDstu2() throws Exception { - String wrongFhirVersion = "3.0.1"; + String wrongFhirVersion = "3.0.2"; assertThat(wrongFhirVersion, is(FhirVersionEnum.DSTU3.getFhirVersionString())); // asserting that what we assume to be the DSTU3 FHIR version is still correct Conformance conf = new Conformance(); conf.setFhirVersion(wrongFhirVersion); @@ -119,7 +119,7 @@ public class ClientServerValidationTestHl7OrgDstu2 { myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/1")); fail(); } catch (FhirClientInappropriateForServerException e) { - assertThat(e.toString(), containsString("The server at base URL \"http://foo/metadata\" returned a conformance statement indicating that it supports FHIR version \"3.0.1\" which corresponds to DSTU3, but this client is configured to use DSTU2_HL7ORG (via the FhirContext)")); + assertThat(e.toString(), containsString("The server at base URL \"http://foo/metadata\" returned a conformance statement indicating that it supports FHIR version \"3.0.2\" which corresponds to DSTU3, but this client is configured to use DSTU2_HL7ORG (via the FhirContext)")); } } diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index cdea8e99b14..6bcdc740f1f 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -116,6 +116,11 @@ 1.4 true + + com.google.code.gson + gson + true + com.google.code.findbugs @@ -175,6 +180,11 @@ ${project.version} test + + org.awaitility + awaitility + test + @@ -214,12 +224,6 @@ - - org.apache.jena - apache-jena-libs - pom - test - net.sf.json-lib json-lib diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java deleted file mode 100644 index 3558221c4e3..00000000000 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java +++ /dev/null @@ -1,367 +0,0 @@ -package org.hl7.fhir.r4.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.CodeType; -import org.hl7.fhir.r4.model.DomainResource; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.UriType; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class DefaultProfileValidationSupport implements IValidationSupport { - - private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/"; - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class); - - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; - - private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set theWantCodes, List theConcepts) { - for (ConceptDefinitionComponent next : theConcepts) { - if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) { - theRetVal - .addContains() - .setSystem(theInclude.getSystem()) - .setCode(next.getCode()) - .setDisplay(next.getDisplay()); - } - addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept()); - } - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet()); - - Set wantCodes = new HashSet<>(); - for (ConceptReferenceComponent next : theInclude.getConcept()) { - wantCodes.add(next.getCode()); - } - - CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); - if (system != null) { - List concepts = system.getConcept(); - addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts); - } - - for (UriType next : theInclude.getValueSet()) { - ValueSet vs = myValueSets.get(defaultString(next.getValueAsString())); - if (vs != null) { - for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) { - ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude); - retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains()); - } - } - } - - return retVal; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - retVal.addAll(myCodeSystems.values()); - retVal.addAll(myStructureDefinitions.values()); - retVal.addAll(myValueSets.values()); - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList<>(provideStructureDefinitionMap(theContext).values()); - } - - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); - } - - private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { - synchronized (this) { - Map codeSystems = myCodeSystems; - Map valueSets = myValueSets; - if (codeSystems == null || valueSets == null) { - codeSystems = new HashMap<>(); - valueSets = new HashMap<>(); - - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r4/model/valueset/valuesets.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r4/model/valueset/v2-tables.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r4/model/valueset/v3-codesystems.xml"); - - myCodeSystems = codeSystems; - myValueSets = valueSets; - } - - // System can take the form "http://url|version" - String system = theSystem; - if (system.contains("|")) { - String version = system.substring(system.indexOf('|') + 1); - if (version.matches("^[0-9.]+$")) { - system = system.substring(0, system.indexOf('|')); - } - } - - if (codeSystem) { - return codeSystems.get(system); - } else { - return valueSets.get(system); - } - } - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - Validate.notBlank(theUri, "theUri must not be null or blank"); - - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - - if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { - return (T) fetchValueSet(theContext, theUri); - } - - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { - String url = theUrl; - if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { - // no change - } else if (url.indexOf('/') == -1) { - url = URL_PREFIX_STRUCTURE_DEFINITION + url; - } else if (StringUtils.countMatches(url, '/') == 1) { - url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; - } - return provideStructureDefinitionMap(theContext).get(url); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); - } - - public void flush() { - myCodeSystems = null; - myStructureDefinitions = null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - if (isBlank(theSystem) || Constants.codeSystemNotNeeded(theSystem)) { - return false; - } - CodeSystem cs = fetchCodeSystem(theContext, theSystem); - return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; - } - - @Override - public boolean isValueSetSupported(FhirContext theContext, String theValueSetUrl) { - return isNotBlank(theValueSetUrl) && fetchValueSet(theContext, theValueSetUrl) != null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - private void loadCodeSystems(FhirContext theContext, Map theCodeSystems, Map theValueSets, String theClasspath) { - ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); - InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - InputStreamReader reader = null; - if (inputStream != null) { - try { - reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof CodeSystem) { - CodeSystem nextValueSet = (CodeSystem) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextValueSet); - } - } else if (next.getResource() instanceof ValueSet) { - ValueSet nextValueSet = (ValueSet) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theValueSets.put(system, nextValueSet); - } - } - } - } finally { - try { - if (reader != null) { - reader.close(); - } - inputStream.close(); - } catch (IOException e) { - ourLog.warn("Failure closing stream", e); - } - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private void loadStructureDefinitions(FhirContext theContext, Map theCodeSystems, String theClasspath) { - ourLog.info("Loading structure definitions from classpath: {}", theClasspath); - InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - if (valuesetText != null) { - InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof StructureDefinition) { - StructureDefinition nextSd = (StructureDefinition) next.getResource(); - nextSd.getText().setDivAsString(""); - String system = nextSd.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextSd); - } - } - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private Map provideStructureDefinitionMap(FhirContext theContext) { - Map structureDefinitions = myStructureDefinitions; - if (structureDefinitions == null) { - structureDefinitions = new HashMap<>(); - - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-resources.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-types.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-others.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/extension/extension-definitions.xml"); - - myStructureDefinitions = structureDefinitions; - } - return structureDefinitions; - } - - private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List conceptList, boolean theCaseSensitive) { - String code = theCode; - if (theCaseSensitive == false) { - code = code.toUpperCase(); - } - - return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code); - } - - private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List conceptList, boolean theCaseSensitive, String code) { - CodeValidationResult retVal = null; - for (ConceptDefinitionComponent next : conceptList) { - String nextCandidate = next.getCode(); - if (theCaseSensitive == false) { - nextCandidate = nextCandidate.toUpperCase(); - } - if (nextCandidate.equals(code)) { - retVal = new CodeValidationResult(null, null, next, next.getDisplay()); - break; - } - - // recurse - retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive); - if (retVal != null) { - break; - } - } - - if (retVal != null) { - retVal.setCodeSystemName(theCodeSystem.getName()); - retVal.setCodeSystemVersion(theCodeSystem.getVersion()); - } - - return retVal; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - if (isNotBlank(theValueSetUrl)) { - ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this)); - try { - ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl); - if (valueSet != null) { - ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null); - ValueSetExpansionComponent expansion = expanded.getValueset().getExpansion(); - for (ValueSet.ValueSetExpansionContainsComponent nextExpansionCode : expansion.getContains()) { - - if (theCode.equals(nextExpansionCode.getCode())) { - if (Constants.codeSystemNotNeeded(theCodeSystem) || nextExpansionCode.getSystem().equals(theCodeSystem)) { - return new CodeValidationResult(new CodeSystem.ConceptDefinitionComponent(new CodeType(theCode))); - } - } - } - - } - } catch (Exception e) { - return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage()); - } - - return null; - } - - if (theCodeSystem != null) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return validateCode(theContext, theSystem, theCode, null, (String) null).asLookupCodeResult(theSystem, theCode); - } - -} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java index 641bf794cd7..ec2ef5edd07 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java @@ -26,13 +26,12 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.*; -import org.hl7.fhir.r4.hapi.fluentpath.FluentPathR4; +import org.hl7.fhir.r4.hapi.fluentpath.FhirPathR4; import org.hl7.fhir.r4.hapi.rest.server.R4BundleFactory; import org.hl7.fhir.r4.model.*; import ca.uhn.fhir.context.*; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; @@ -43,13 +42,8 @@ public class FhirR4 implements IFhirVersion { private String myId; @Override - public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) { - return new FluentPathR4(theFhirContext); - } - - @Override - public IContextValidationSupport createValidationSupport() { - return ReflectionUtil.newInstanceOfFhirProfileValidationSupport("org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport"); + public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) { + return new FhirPathR4(theFhirContext); } @Override diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java index 46d0a2fbd2f..21e25668efb 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java @@ -1,10 +1,9 @@ package org.hl7.fhir.r4.hapi.ctx; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.CoverageIgnore; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -21,19 +20,24 @@ import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.r4.terminologies.ValueSetExpanderFactory; -import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; import org.hl7.fhir.r4.utils.IResourceValidator; -import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TranslationServices; +import org.hl7.fhir.utilities.i18n.I18nBase; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationOptions; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory { +public final class HapiWorkerContext extends I18nBase implements IWorkerContext { private final FhirContext myCtx; private final Cache myFetchedResourceCache; private IValidationSupport myValidationSupport; @@ -52,11 +56,14 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build(); + + // Set a default locale + setValidationMessageLanguage(getLocale()); } @Override public List allStructures() { - return myValidationSupport.fetchAllStructureDefinitions(myCtx); + return myValidationSupport.fetchAllStructureDefinitions(); } @Override @@ -69,7 +76,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return null; } else { - return myValidationSupport.fetchCodeSystem(myCtx, theSystem); + return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem); } } @@ -83,13 +90,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander throw new UnsupportedOperationException(); } - @Override - public ValueSetExpander getExpander() { - ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this); - retVal.setMaxExpansionSize(Integer.MAX_VALUE); - return retVal; - } - @Override public org.hl7.fhir.r4.utils.INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) { throw new UnsupportedOperationException(); @@ -140,7 +140,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return false; } else { - return myValidationSupport.isCodeSystemSupported(myCtx, theSystem); + return myValidationSupport.isCodeSystemSupported(myValidationSupport, theSystem); } } @@ -152,7 +152,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, CodeableConcept theCode, ValueSet theVs) { + public ValidationResult validateCode(ValidationOptions theOptions, CodeableConcept theCode, ValueSet theVs) { for (Coding next : theCode.getCoding()) { ValidationResult retVal = validateCode(theOptions, next, theVs); if (retVal.isOk()) { @@ -164,7 +164,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, Coding theCode, ValueSet theVs) { + public ValidationResult validateCode(ValidationOptions theOptions, Coding theCode, ValueSet theVs) { String system = theCode.getSystem(); String code = theCode.getCode(); String display = theCode.getDisplay(); @@ -172,48 +172,35 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) { - IContextValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, (String)null); + public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay) { + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, null); if (result == null) { return null; } - return new ValidationResult((IssueSeverity)result.getSeverity(), result.getMessage(), (ConceptDefinitionComponent)result.asConceptDefinition()); + + IssueSeverity severity = null; + if (result.getSeverity() != null) { + severity = IssueSeverity.fromCode(result.getSeverityCode()); + } + + ConceptDefinitionComponent definition = new ConceptDefinitionComponent().setCode(result.getCode()); + return new ValidationResult(severity, result.getMessage(), definition); } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) { + public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) { throw new UnsupportedOperationException(); } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ValueSet theVs) { - - /* - * The following valueset is a special case, since the BCP codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } - - /* - * The following valueset is a special case, since the mime types codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } + public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay, ValueSet theVs) { IValidationSupport.CodeValidationResult outcome; if (isNotBlank(theVs.getUrl())) { - outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl()); + outcome = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs.getUrl()); } else { - outcome = myValidationSupport.validateCodeInValueSet(myCtx, theSystem, theCode, theDisplay, theVs); + outcome = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs); } if (outcome != null && outcome.isOk()) { @@ -227,8 +214,9 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) { - return validateCode(theOptions, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, vs); + public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet vs) { + ValidationOptions options = theOptions.guessSystem(); + return validateCode(options, null, code, null, vs); } @Override @@ -259,30 +247,16 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValueSetExpansionOutcome expand(ValueSet theSource, Parameters theProfile) { - ValueSetExpansionOutcome vso; - try { - vso = getExpander().expand(theSource, theProfile); - } catch (InvalidRequestException e) { - throw e; - } catch (Exception e) { - throw new InternalErrorException(e); - } - if (vso.getError() != null) { - throw new InvalidRequestException(vso.getError()); - } else { - return vso; - } - } - - @Override - public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) { + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHierarchical) { throw new UnsupportedOperationException(); } @Override - public ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException { - return myValidationSupport.expandValueSet(myCtx, theInc); + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHierarchical) throws TerminologyServiceException { + ValueSet input = new ValueSet(); + input.getCompose().addInclude(theInc); + IValidationSupport.ValueSetExpansionOutcome output = myValidationSupport.expandValueSet(myValidationSupport, null, input); + return new ValueSetExpander.ValueSetExpansionOutcome((ValueSet) output.getValueSet(), output.getError(), null); } @Override @@ -361,9 +335,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander return null; } else { @SuppressWarnings("unchecked") - T retVal = (T) myFetchedResourceCache.get(theUri, t -> { - return myValidationSupport.fetchResource(myCtx, theClass, theUri); - }); + T retVal = (T) myFetchedResourceCache.get(theUri, t -> myValidationSupport.fetchResource(theClass, theUri)); return retVal; } } @@ -398,8 +370,16 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException { + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHierarchical) throws FHIRException { throw new UnsupportedOperationException(); } + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; + } + } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java deleted file mode 100644 index cc8f317b0bd..00000000000 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.hl7.fhir.r4.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.util.List; - -public interface IValidationSupport - extends ca.uhn.fhir.context.support.IContextValidationSupport { - - /** - * Expands the given portion of a ValueSet - * - * @param theInclude The portion to include - * @return The expansion - */ - @Override - ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude); - - /** - * Load and return all possible structure definitions - */ - @Override - List fetchAllStructureDefinitions(FhirContext theContext); - - /** - * Fetch a code system by Uri - * - * @param uri Canonical Uri of the code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String uri); - - /** - * Fetch a valueset by Uri - * - * @param uri Canonical Uri of the ValueSet - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - ValueSet fetchValueSet(FhirContext theContext, String uri); - - /** - * Loads a resource needed by the validation (a StructureDefinition, or a - * ValueSet) - * - * @param theContext The HAPI FHIR Context object current in use by the validator - * @param theClass The type of the resource to load - * @param theUri The resource URI - * @return Returns the resource, or null if no resource with the - * given URI can be found - */ - @Override - T fetchResource(FhirContext theContext, Class theClass, String theUri); - - @Override - StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl); - - /** - * Returns true if codes in the given code system can be expanded - * or validated - * - * @param theSystem The URI for the code system, e.g. "http://loinc.org" - * @return Returns true if codes in the given code system can be - * validated - */ - @Override - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); - - /** - * Returns true if the given valueset can be validated by the given - * validation support module - * - * @param theContext The FHIR context - * @param theValueSetUrl The URL - */ - default boolean isValueSetSupported(FhirContext theContext, String theValueSetUrl) { - return false; - } - - /** - * Generate a snapshot from the given differential profile. - * - * @return Returns null if this module does not know how to handle this request - */ - StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName); - -} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java similarity index 58% rename from hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java rename to hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java index f85d63a6472..eb1ca366b43 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java @@ -1,27 +1,24 @@ package org.hl7.fhir.r4.hapi.fluentpath; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.fluentpath.FluentPathExecutionException; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.fhirpath.FhirPathExecutionException; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.utils.FHIRPathEngine; import java.util.List; import java.util.Optional; -public class FluentPathR4 implements IFluentPath { +public class FhirPathR4 implements IFhirPath { private FHIRPathEngine myEngine; - public FluentPathR4(FhirContext theCtx) { - if (!(theCtx.getValidationSupport() instanceof IValidationSupport)) { - throw new IllegalStateException("Validation support module configured on context appears to be for the wrong FHIR version- Does not extend " + IValidationSupport.class.getName()); - } - IValidationSupport validationSupport = (IValidationSupport) theCtx.getValidationSupport(); + public FhirPathR4(FhirContext theCtx) { + IValidationSupport validationSupport = theCtx.getValidationSupport(); myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport)); } @@ -32,12 +29,12 @@ public class FluentPathR4 implements IFluentPath { try { result = myEngine.evaluate((Base) theInput, thePath); } catch (FHIRException e) { - throw new FluentPathExecutionException(e); + throw new FhirPathExecutionException(e); } for (Base next : result) { if (!theReturnType.isAssignableFrom(next.getClass())) { - throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); + throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/context/BaseRuntimeElementDefinitionTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/context/BaseRuntimeElementDefinitionTest.java new file mode 100644 index 00000000000..965a327ef3a --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/context/BaseRuntimeElementDefinitionTest.java @@ -0,0 +1,23 @@ +package ca.uhn.fhir.context; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class BaseRuntimeElementDefinitionTest { + + @Test + public void testNewInstance_InvalidArgumentType() { + FhirContext ctx = FhirContext.forR4(); + + BaseRuntimeElementDefinition def = ctx.getElementDefinition("string"); + + try { + def.newInstance(123); + fail(); + } catch (ConfigurationException e) { + assertEquals("Failed to instantiate type:org.hl7.fhir.r4.model.StringType", e.getMessage()); + } + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 5527e7602b6..87b8b88d6aa 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -26,8 +26,10 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class JsonParserR4Test extends BaseTest { private static final Logger ourLog = LoggerFactory.getLogger(JsonParserR4Test.class); @@ -150,6 +152,25 @@ public class JsonParserR4Test extends BaseTest { } + @Test + public void testParseBundleWithMultipleNestedContainedResources() throws Exception { + String text = loadResource("/bundle-with-two-patient-resources.json"); + + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text); + assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + + assertEquals("12346", getPatientIdValue(bundle, 0)); + assertEquals("12345", getPatientIdValue(bundle, 1)); + } + + private String getPatientIdValue(Bundle input, int entry) { + final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource(); + final Patient patient = (Patient) documentReference.getSubject().getResource(); + return patient.getIdentifier().get(0).getValue(); + } + /** * See #814 */ @@ -248,6 +269,12 @@ public class JsonParserR4Test extends BaseTest { } + @Test + public void testParseSingleQuotes() { + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, "{ 'resourceType': 'Bundle', 'id': '123' }"); + assertEquals("123", bundle.getIdElement().getIdPart()); + } + @Test public void testEncodeBinary() { @@ -260,6 +287,18 @@ public class JsonParserR4Test extends BaseTest { assertEquals("{\"resourceType\":\"Binary\",\"contentType\":\"application/octet-stream\",\"data\":\"AAECAwQ=\"}", output); } + + @Test + public void testAlwaysUseUnixNewlines() { + Patient p = new Patient(); + p.setId("1"); + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); + assertEquals("{\n" + + " \"resourceType\": \"Patient\",\n" + + " \"id\": \"1\"\n" + + "}", encoded); + } + @Test public void testEncodeWithInvalidExtensionMissingUrl() { @@ -311,7 +350,7 @@ public class JsonParserR4Test extends BaseTest { parser.encodeResourceToString(p); fail(); } catch (DataFormatException e) { - assertEquals("Extension contains both a value and nested extensions: Patient(res).extension", e.getMessage()); + assertEquals("[element=\"Patient(res).extension\"] Extension contains both a value and nested extensions", e.getMessage()); } } @@ -530,6 +569,32 @@ public class JsonParserR4Test extends BaseTest { } + /** + * See #1793 + */ + @Test + public void testParseEmptyAttribute() { + String input = "{\n" + + " \"resourceType\": \"Patient\",\n" + + " \"identifier\": [\n" + + " {\n" + + " \"system\": \"https://example.com\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " ]\n" + + "}"; + + IParser jsonParser = ourCtx.newJsonParser(); + jsonParser.setParserErrorHandler(new StrictErrorHandler()); + try { + jsonParser.parseResource(Patient.class, input); + fail(); + } catch (DataFormatException e) { + assertEquals("[element=\"value\"] Invalid attribute value \"\": Attribute value must not be empty (\"\")", e.getMessage()); + } + + } + @Test public void testParseExtensionOnPrimitive() throws IOException { String input = IOUtils.toString(JsonParserR4Test.class.getResourceAsStream("/extension-on-line.txt"), Constants.CHARSET_UTF8); @@ -586,6 +651,14 @@ public class JsonParserR4Test extends BaseTest { * 15:20:41.708 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1200 passes - 28ms / pass - 34.5 / second * 15:20:44.722 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1300 passes - 29ms / pass - 34.4 / second * 15:20:47.716 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:574] - Encoded 1400 passes - 29ms / pass - 34.4 / second + * + * 2020-02-27 - Post #1673 + * 21:27:25.817 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1100 passes - 28ms / pass - 35.5 / second + * 21:27:28.598 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1200 passes - 28ms / pass - 35.5 / second + * 21:27:31.436 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1300 passes - 28ms / pass - 35.5 / second + * 21:27:34.246 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1400 passes - 28ms / pass - 35.5 / second + * 21:27:37.013 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1500 passes - 28ms / pass - 35.6 / second + * 21:27:39.874 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:609] - Encoded 1600 passes - 28ms / pass - 35.5 / second */ @Test @Ignore @@ -655,6 +728,12 @@ public class JsonParserR4Test extends BaseTest { * 15:22:40.699 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:638] - Parsed 2500 passes - 12ms / pass - 79.7 / second * 15:22:42.135 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:638] - Parsed 2600 passes - 12ms / pass - 79.3 / second * + * 2020-02-27 - Post #1673 + * 21:29:38.157 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2200 passes - 11ms / pass - 83.4 / second + * 21:29:39.374 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2300 passes - 12ms / pass - 83.3 / second + * 21:29:40.576 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2400 passes - 12ms / pass - 83.3 / second + * 21:29:41.778 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2500 passes - 12ms / pass - 83.3 / second + * 21:29:42.999 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:687] - Parsed 2600 passes - 12ms / pass - 83.3 / second * */ @Test @@ -736,6 +815,40 @@ public class JsonParserR4Test extends BaseTest { return b; } + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testEncodeContainedBundle() { + String auditEvent = "{\n" + + " \"resourceType\": \"AuditEvent\",\n" + + " \"contained\": [ {\n" + + " \"resourceType\": \"Bundle\",\n" + + " \"id\": \"REASONS\",\n" + + " \"entry\": [ {\n" + + " \"resource\": {\n" + + " \"resourceType\": \"Condition\",\n" + + " \"id\": \"123\"\n" + + " }\n" + + " } ]\n" + + " }, {\n" + + " \"resourceType\": \"MeasureReport\",\n" + + " \"id\": \"MRPT5000602611RD\",\n" + + " \"evaluatedResource\": [ {\n" + + " \"reference\": \"#REASONS\"\n" + + " } ]\n" + + " } ],\n" + + " \"entity\": [ {\n" + + " \"what\": {\n" + + " \"reference\": \"#MRPT5000602611RD\"\n" + + " }\n" + + " } ]\n" + + "}"; + AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); + String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, auditEventAsString); + } + @AfterClass public static void afterClassClearContext() { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index c16ac5c8950..536dce99b7e 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -7,18 +7,26 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import ca.uhn.fhir.test.BaseTest; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AuditEvent; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Narrative; import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.FhirContext; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; import java.io.IOException; +import java.net.URL; public class XmlParserR4Test extends BaseTest { private static final Logger ourLog = LoggerFactory.getLogger(XmlParserR4Test.class); @@ -79,6 +87,23 @@ public class XmlParserR4Test extends BaseTest { } + @Test + public void testParseBundleWithMultipleNestedContainedResources() throws Exception { + URL url = Resources.getResource("bundle-with-two-patient-resources.xml"); + String text = Resources.toString(url, Charsets.UTF_8); + + Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, text); + + assertEquals("12346", getPatientIdValue(bundle, 0)); + assertEquals("12345", getPatientIdValue(bundle, 1)); + } + + private String getPatientIdValue(Bundle input, int entry) { + final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource(); + final Patient patient = (Patient) documentReference.getSubject().getResource(); + return patient.getIdentifier().get(0).getValue(); + } + /** * See #1658 */ @@ -91,5 +116,43 @@ public class XmlParserR4Test extends BaseTest { ourLog.info(encoded); } + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testEncodeContainedBundle() { + String auditEvent = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + AuditEvent ae = ourCtx.newXmlParser().parseResource(AuditEvent.class, auditEvent); + String auditEventAsString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, auditEventAsString); + } + + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java index 72841e5450a..dcb523ac20e 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/jsonlike/JsonLikeParserTest.java @@ -1,5 +1,26 @@ package ca.uhn.fhir.parser.jsonlike; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IJsonLikeParser; +import ca.uhn.fhir.parser.json.JsonLikeArray; +import ca.uhn.fhir.parser.json.JsonLikeObject; +import ca.uhn.fhir.parser.json.JsonLikeStructure; +import ca.uhn.fhir.parser.json.JsonLikeValue; +import ca.uhn.fhir.parser.json.JsonLikeWriter; +import ca.uhn.fhir.parser.json.jackson.JacksonStructure; +import ca.uhn.fhir.parser.view.ExtPatient; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -14,31 +35,6 @@ import java.util.Map; import java.util.Set; import java.util.Stack; -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Extension; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IJsonLikeParser; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.parser.json.GsonStructure; -import ca.uhn.fhir.parser.json.JsonLikeArray; -import ca.uhn.fhir.parser.json.JsonLikeObject; -import ca.uhn.fhir.parser.json.JsonLikeStructure; -import ca.uhn.fhir.parser.json.JsonLikeValue; -import ca.uhn.fhir.parser.json.JsonLikeWriter; -import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; -import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; -import ca.uhn.fhir.parser.view.ExtPatient; -import ca.uhn.fhir.util.TestUtil; - public class JsonLikeParserTest { private static FhirContext ourCtx = FhirContext.forR4(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserTest.class); @@ -55,7 +51,7 @@ public class JsonLikeParserTest { String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); ourLog.info(encoded); - JsonLikeStructure jsonLikeStructure = new GsonStructure(); + JsonLikeStructure jsonLikeStructure = new JacksonStructure(); jsonLikeStructure.load(new StringReader(encoded)); IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser(); @@ -258,17 +254,6 @@ public class JsonLikeParserTest { return this; } - @Override - public JsonLikeWriter beginArray() throws IOException { - if (currentBlock.getType() == BlockType.NONE) { - throw new IOException("JsonLikeStreamWriter.beginArray() called but only beginObject() is allowed here."); - } - blockStack.push(currentBlock); - currentBlock = new Block(BlockType.ARRAY); - currentBlock.setArray(new ArrayList()); - return this; - } - @Override public JsonLikeWriter beginObject(String name) throws IOException { if (currentBlock.getType() == BlockType.ARRAY) { @@ -429,15 +414,6 @@ public class JsonLikeParserTest { return this; } - @Override - public JsonLikeWriter writeNull(String name) throws IOException { - if (currentBlock.getType() == BlockType.ARRAY) { - throw new IOException("Named JSON elements can only be created in JSON objects"); - } - currentBlock.getObject().put(name, null); - return this; - } - @Override public JsonLikeWriter endObject() throws IOException { if (currentBlock.getType() == BlockType.NONE) { @@ -452,7 +428,7 @@ public class JsonLikeParserTest { } @Override - public JsonLikeWriter endArray() throws IOException { + public JsonLikeWriter endArray() { if (currentBlock.getType() == BlockType.NONE) { ourLog.error("JsonLikeStreamWriter.endArray(); called with no active JSON document"); } else { @@ -465,11 +441,11 @@ public class JsonLikeParserTest { } @Override - public JsonLikeWriter endBlock() throws IOException { + public JsonLikeWriter endBlock() { if (currentBlock.getType() == BlockType.NONE) { ourLog.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document"); } else { - Object toPut = null; + Object toPut; if (currentBlock.getType() == BlockType.ARRAY) { toPut = currentBlock.getArray(); } else { @@ -544,14 +520,9 @@ public class JsonLikeParserTest { return jsonLikeObject; } - @Override - public JsonLikeArray getRootArray() throws DataFormatException { - throw new DataFormatException("JSON document must be an object not an array for native Java Map structures"); - } - private class JsonMapObject extends JsonLikeObject { private Map nativeObject; - private Map jsonLikeMap = new LinkedHashMap(); + private Map jsonLikeMap = new LinkedHashMap<>(); public JsonMapObject (Map json) { this.nativeObject = json; @@ -585,7 +556,7 @@ public class JsonLikeParserTest { private class JsonMapArray extends JsonLikeArray { private List nativeArray; - private Map jsonLikeMap = new LinkedHashMap(); + private Map jsonLikeMap = new LinkedHashMap<>(); public JsonMapArray (List json) { this.nativeArray = json; @@ -603,7 +574,7 @@ public class JsonLikeParserTest { @Override public JsonLikeValue get(int index) { - Integer key = Integer.valueOf(index); + Integer key = index; JsonLikeValue result = null; if (jsonLikeMap.containsKey(key)) { result = jsonLikeMap.get(key); @@ -694,7 +665,7 @@ public class JsonLikeParserTest { @Override public boolean getAsBoolean() { if (nativeValue != null && isBoolean()) { - return ((Boolean)nativeValue).booleanValue(); + return (Boolean) nativeValue; } return super.getAsBoolean(); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BaseGenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BaseGenericClientR4Test.java new file mode 100644 index 00000000000..a8f941668a5 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BaseGenericClientR4Test.java @@ -0,0 +1,145 @@ +package ca.uhn.fhir.rest.client; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.impl.BaseClient; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.VersionUtil; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class BaseGenericClientR4Test { + protected static FhirContext ourCtx; + protected int myAnswerCount; + protected HttpClient myHttpClient; + protected HttpResponse myHttpResponse; + + @Before + public void before() { + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + myAnswerCount = 0; + System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true"); + } + + private String expectedUserAgent() { + return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.R4.getFhirVersionString() + "/R4; apache)"; + } + + protected byte[] extractBodyAsByteArray(ArgumentCaptor capt) throws IOException { + byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent()); + return body; + } + + protected String extractBodyAsString(ArgumentCaptor capt) throws IOException { + String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), StandardCharsets.UTF_8); + return body; + } + + protected ArgumentCaptor prepareClientForSearchResponse() throws IOException { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) { + return new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8); + } + }); + return capt; + } + + protected ArgumentCaptor prepareClientForCapabilityStatement() throws IOException { + final String msg = "{\"resourceType\":\"CapabilityStatement\", \"fhirVersion\":\"4.0.1\"}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) { + return new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8); + } + }); + return capt; + } + + protected ArgumentCaptor prepareClientForCreateResponse() throws IOException { + final String msg = "{\"resourceType\":\"Patient\",\"id\":\"123\",\"active\":true}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) { + return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")}; + } + }); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) { + return new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8); + } + }); + return capt; + } + + protected List> toTypeList(Class theClass) { + ArrayList> retVal = new ArrayList>(); + retVal.add(theClass); + return retVal; + } + + protected void validateUserAgent(ArgumentCaptor capt) { + assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); + assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forR4(); + } +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java index c7adb44e2c1..5dc23a10552 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.rest.client; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; @@ -9,13 +7,17 @@ import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.CustomTypeR4Test; import ca.uhn.fhir.parser.CustomTypeR4Test.MyCustomPatient; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; -import ca.uhn.fhir.rest.client.impl.BaseClient; import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor; import ca.uhn.fhir.rest.client.interceptor.UserInfoInterceptor; import ca.uhn.fhir.rest.param.DateParam; @@ -23,32 +25,24 @@ import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; -import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; -import ca.uhn.fhir.util.VersionUtil; import com.google.common.base.Charsets; import com.helger.commons.io.stream.StringInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.Header; -import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleType; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -56,77 +50,34 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class GenericClientR4Test { +public class GenericClientR4Test extends BaseGenericClientR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientR4Test.class); - private static FhirContext ourCtx; - private int myAnswerCount; - private HttpClient myHttpClient; - private HttpResponse myHttpResponse; - - @Before - public void before() { - myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); - ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); - myAnswerCount = 0; - System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true"); - } - - private String expectedUserAgent() { - return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.R4.getFhirVersionString() + "/R4; apache)"; - } - - private byte[] extractBodyAsByteArray(ArgumentCaptor capt) throws IOException { - byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent()); - return body; - } - - private String extractBodyAsString(ArgumentCaptor capt) throws IOException { - String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), "UTF-8"); - return body; - } - - private ArgumentCaptor prepareClientForSearchResponse() throws IOException { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); - return capt; - } @Test public void testAcceptHeaderCustom() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -183,7 +134,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -193,7 +144,7 @@ public class GenericClientR4Test { pt.getText().setDivAsString("A PATIENT"); Binary bin = new Binary(); - bin.setContent(ourCtx.newJsonParser().encodeResourceToString(pt).getBytes("UTF-8")); + bin.setContent(ourCtx.newJsonParser().encodeResourceToString(pt).getBytes(StandardCharsets.UTF_8)); bin.setContentType(Constants.CT_FHIR_JSON); client.create().resource(bin).execute(); @@ -207,7 +158,7 @@ public class GenericClientR4Test { Binary output = ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)); assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); - Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8")); + Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), StandardCharsets.UTF_8)); assertEquals("
        A PATIENT
        ", outputPt.getText().getDivAsString()); } @@ -226,7 +177,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -290,7 +241,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -330,9 +281,9 @@ public class GenericClientR4Test { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), StandardCharsets.UTF_8); } else { - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); } } }); @@ -379,7 +330,7 @@ public class GenericClientR4Test { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); } }); @@ -400,6 +351,78 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); } + @Test + public void testDeleteCascade() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + OperationOutcome oo = new OperationOutcome(); + oo.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) { + return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")}; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) { + myAnswerCount++; + return new ReaderInputStream(new StringReader(p.encodeResourceToString(oo)), StandardCharsets.UTF_8); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + MethodOutcome outcome; + + // Regular delete + outcome = client + .delete() + .resourceById(new IdType("Patient/222")) + .execute(); + assertNotNull(outcome); + assertEquals(1, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(myAnswerCount - 1).getURI().toASCIIString()); + assertEquals("DELETE", capt.getAllValues().get(myAnswerCount - 1).getMethod()); + + // NONE Cascading delete + outcome = client + .delete() + .resourceById(new IdType("Patient/222")) + .cascade(DeleteCascadeModeEnum.NONE) + .execute(); + assertNotNull(outcome); + assertEquals(2, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(myAnswerCount - 1).getURI().toASCIIString()); + assertEquals("DELETE", capt.getAllValues().get(myAnswerCount - 1).getMethod()); + + // DELETE Cascading delete + outcome = client + .delete() + .resourceById(new IdType("Patient/222")) + .cascade(DeleteCascadeModeEnum.DELETE) + .execute(); + assertNotNull(outcome); + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222?" + Constants.PARAMETER_CASCADE_DELETE + "=" + Constants.CASCADE_DELETE, capt.getAllValues().get(myAnswerCount - 1).getURI().toASCIIString()); + assertEquals("DELETE", capt.getAllValues().get(myAnswerCount - 1).getMethod()); + + // DELETE Cascading delete on search URL + outcome = client + .delete() + .resourceConditionalByUrl("Patient?identifier=sys|val") + .cascade(DeleteCascadeModeEnum.DELETE) + .execute(); + assertNotNull(outcome); + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient?identifier=sys%7Cval&" + Constants.PARAMETER_CASCADE_DELETE + "=" + Constants.CASCADE_DELETE, capt.getAllValues().get(myAnswerCount - 1).getURI().toASCIIString()); + assertEquals("DELETE", capt.getAllValues().get(myAnswerCount - 1).getMethod()); + } + @Test public void testExplicitCustomTypeHistoryType() throws Exception { final String respString = CustomTypeR4Test.createBundle(CustomTypeR4Test.createResource(false)); @@ -410,7 +433,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -437,7 +460,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -482,7 +505,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -522,7 +545,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -567,7 +590,7 @@ public class GenericClientR4Test { respString = p.encodeResourceToString(conf); } myCount++; - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -615,7 +638,7 @@ public class GenericClientR4Test { respString = p.encodeResourceToString(conf); } myAnswerCount++; - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -655,7 +678,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> { IParser p = ourCtx.newXmlParser(); - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); }); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -670,6 +693,40 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/_history?_at=ge2011&_at=le2018", capt.getAllValues().get(0).getURI().toASCIIString()); } + + @Test + public void testHistoryOnTypeString() throws Exception { + + final Bundle resp1 = new Bundle(); + resp1.setTotal(0); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) { + return new Header[0]; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> { + IParser p = ourCtx.newXmlParser(); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle outcome = client + .history() + .onType("Patient") + .returnBundle(Bundle.class) + .execute(); + + assertEquals(0, outcome.getTotal()); + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(0).getURI().toASCIIString()); + } + @Test public void testHttp499() throws Exception { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); @@ -782,7 +839,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -826,7 +883,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -870,7 +927,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -913,7 +970,7 @@ public class GenericClientR4Test { when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "text/html")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"))); + when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8)); when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{ new BasicHeader("content-type", "text/html") }); @@ -948,7 +1005,7 @@ public class GenericClientR4Test { Parameters inputParams = new Parameters(); inputParams.addParameter().setName("name").setValue(new BooleanType(true)); - final byte[] respBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,100}; + final byte[] respBytes = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100}; ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); @@ -993,7 +1050,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1047,7 +1104,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1086,7 +1143,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1125,7 +1182,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1163,7 +1220,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1201,7 +1258,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1239,7 +1296,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1286,7 +1343,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(encoded), StandardCharsets.UTF_8); } }); @@ -1331,7 +1388,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(encoded), StandardCharsets.UTF_8); } }); @@ -1360,7 +1417,7 @@ public class GenericClientR4Test { when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8)); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -1389,7 +1446,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1420,7 +1477,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -1459,18 +1516,7 @@ public class GenericClientR4Test { @Test public void testSearchByDate() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -1478,6 +1524,8 @@ public class GenericClientR4Test { DateTimeDt now = DateTimeDt.withCurrentTime(); String dateString = now.getValueAsString().substring(0, 10); + DateTimeDt nowWithMillis = new DateTimeDt(new Date(), TemporalPrecisionEnum.MILLI, TimeZone.getDefault()); + client.search() .forResource("Patient") .where(Patient.BIRTHDATE.after().day(dateString)) @@ -1550,6 +1598,24 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/Patient?birthdate=gt" + now.getValueAsString(), UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); idx++; + client.search() + .forResource("Patient") + .where(Patient.BIRTHDATE.after().millis("2011-01-02T22:33:01.123Z")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?birthdate=gt2011-01-02T22:33:01.123Z", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + idx++; + + client.search() + .forResource("Patient") + .where(Patient.BIRTHDATE.after().millis(nowWithMillis.getValue())) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?birthdate=gt" + nowWithMillis.getValueAsString(), UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + idx++; + client.search() .forResource("Patient") .where(Patient.BIRTHDATE.after().now()) @@ -1563,21 +1629,9 @@ public class GenericClientR4Test { idx++; } - @SuppressWarnings("deprecation") @Test public void testSearchByQuantity() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -1676,18 +1730,7 @@ public class GenericClientR4Test { @Test public void testSearchByString() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -1768,18 +1811,7 @@ public class GenericClientR4Test { @Test public void testSearchByUrl() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -1813,12 +1845,12 @@ public class GenericClientR4Test { when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8)); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); HashMap> params = new HashMap>(); - params.put("foo", Arrays.asList((IQueryParameterType) new DateParam("2001"))); + params.put("foo", Arrays.asList(new DateParam("2001"))); Bundle response = client .search() .forResource(Patient.class) @@ -1861,7 +1893,7 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8)); // httpResponse = new BasicHttpResponse(statusline, catalog, locale) when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); @@ -1877,18 +1909,7 @@ public class GenericClientR4Test { @Test public void testSearchWithNullParameters() throws Exception { - final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).then(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); + ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int idx = 0; @@ -1923,7 +1944,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> { IParser p = ourCtx.newXmlParser(); - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); }); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -1961,7 +1982,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2014,7 +2035,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> { IParser p = ourCtx.newXmlParser(); - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); }); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -2093,7 +2114,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2140,9 +2161,9 @@ public class GenericClientR4Test { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), StandardCharsets.UTF_8); } else { - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); } } }); @@ -2188,7 +2209,7 @@ public class GenericClientR4Test { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), StandardCharsets.UTF_8); } }); @@ -2225,7 +2246,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2262,7 +2283,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2283,7 +2304,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2318,7 +2339,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), StandardCharsets.UTF_8); } }); @@ -2351,7 +2372,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2389,7 +2410,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + return new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8); } }); @@ -2400,26 +2421,5 @@ public class GenericClientR4Test { assertEquals(MyPatientWithExtensions.class, bundle.getEntry().get(0).getResource().getClass()); } - private List> toTypeList(Class theClass) { - ArrayList> retVal = new ArrayList>(); - retVal.add(theClass); - return retVal; - } - - private void validateUserAgent(ArgumentCaptor capt) { - assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); - assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void beforeClass() { - ourCtx = FhirContext.forR4(); - } - } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptorTest.java new file mode 100644 index 00000000000..3f2587651aa --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/interceptor/UrlTenantSelectionInterceptorTest.java @@ -0,0 +1,78 @@ +package ca.uhn.fhir.rest.client.interceptor; + +import ca.uhn.fhir.rest.client.BaseGenericClientR4Test; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.Assert.assertEquals; + +public class UrlTenantSelectionInterceptorTest extends BaseGenericClientR4Test { + + @Test + public void testAddTenantToGet() throws Exception { + ArgumentCaptor capt = prepareClientForSearchResponse(); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + client.registerInterceptor(new UrlTenantSelectionInterceptor("TENANT-A")); + + client + .history() + .onType(Patient.class) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/TENANT-A/Patient/_history", capt.getAllValues().get(0).getURI().toString()); + } + + @Test + public void testAddTenantToGetAtRoot() throws Exception { + ArgumentCaptor capt = prepareClientForSearchResponse(); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com:8000"); + client.registerInterceptor(new UrlTenantSelectionInterceptor("TENANT-A")); + + client + .history() + .onType(Patient.class) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com:8000/TENANT-A/Patient/_history", capt.getAllValues().get(0).getURI().toString()); + } + + @Test + public void testAddTenantToGetMetadataAtRoot() throws Exception { + ArgumentCaptor capt = prepareClientForCapabilityStatement(); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com:8000/"); + client.registerInterceptor(new UrlTenantSelectionInterceptor("TENANT-A")); + + client + .capabilities() + .ofType(CapabilityStatement.class) + .execute(); + + assertEquals("http://example.com:8000/TENANT-A/metadata", capt.getAllValues().get(0).getURI().toString()); + } + + @Test + public void testAddTenantToPost() throws Exception { + ArgumentCaptor capt = prepareClientForCreateResponse(); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + client.registerInterceptor(new UrlTenantSelectionInterceptor("TENANT-A")); + + client + .create() + .resource(new Patient().setActive(true)) + .execute(); + + assertEquals("http://example.com/fhir/TENANT-A/Patient", capt.getAllValues().get(0).getURI().toString()); + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java index dd6793ec4df..a646e95cb5b 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java @@ -91,7 +91,7 @@ public class BlockingContentR4Test { if (myByteCount++ == 10) { ourLog.info("About to block..."); try { - Thread.sleep(30000); + Thread.sleep(3000); } catch (InterruptedException e) { ourLog.warn("Interrupted", e); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchMethodPriorityTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchMethodPriorityTest.java new file mode 100644 index 00000000000..abc313106ee --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchMethodPriorityTest.java @@ -0,0 +1,221 @@ +package ca.uhn.fhir.rest.server; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import com.google.common.collect.Lists; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class SearchMethodPriorityTest { + + @ClassRule + public static RestfulServerRule ourServerRule = new RestfulServerRule(FhirVersionEnum.R4); + + private String myLastMethod; + private IGenericClient myClient; + + @Before + public void before() { + myLastMethod = null; + myClient = ourServerRule.getFhirClient(); + } + + @After + public void after() { + ourServerRule.getRestfulServer().unregisterAllProviders(); + } + + @Test + public void testDateRangeSelectedWhenMultipleParametersProvided() { + ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider()); + + myClient + .search() + .forResource("Patient") + .where(Patient.BIRTHDATE.after().day("2001-01-01")) + .and(Patient.BIRTHDATE.before().day("2002-01-01")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("findDateRangeParam", myLastMethod); + } + + @Test + public void testDateRangeNotSelectedWhenSingleParameterProvided() { + ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider()); + + myClient + .search() + .forResource("Patient") + .where(Patient.BIRTHDATE.after().day("2001-01-01")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("findDateParam", myLastMethod); + } + + @Test + public void testEmptyDateSearchProvidedWithNoParameters() { + ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider()); + + myClient + .search() + .forResource("Patient") + .returnBundle(Bundle.class) + .execute(); + + assertEquals("find", myLastMethod); + } + + @Test + public void testStringAndListSelectedWhenMultipleParametersProvided() { + ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider()); + + myClient + .search() + .forResource("Patient") + .where(Patient.NAME.matches().value("hello")) + .and(Patient.NAME.matches().value("goodbye")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("findStringAndListParam", myLastMethod); + } + + @Test + public void testStringAndListNotSelectedWhenSingleParameterProvided() { + ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider()); + + myClient + .search() + .forResource("Patient") + .where(Patient.NAME.matches().value("hello")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("findString", myLastMethod); + } + + @Test + public void testEmptyStringSearchProvidedWithNoParameters() { + ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider()); + + myClient + .search() + .forResource("Patient") + .returnBundle(Bundle.class) + .execute(); + + assertEquals("find", myLastMethod); + } + + @Test + public void testEmptyStringSearchProvidedWithNoParameters2() { + ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProviderReverseOrder()); + + myClient + .search() + .forResource("Patient") + .returnBundle(Bundle.class) + .execute(); + + assertEquals("find", myLastMethod); + } + + public class DateStrengthsWithRequiredResourceProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Search + public List find() { + myLastMethod = "find"; + return Lists.newArrayList(); + } + + @Search() + public List findDateParam( + @RequiredParam(name = Patient.SP_BIRTHDATE) DateParam theDate) { + myLastMethod = "findDateParam"; + return Lists.newArrayList(); + } + + @Search() + public List findDateRangeParam( + @RequiredParam(name = Patient.SP_BIRTHDATE) DateRangeParam theRange) { + myLastMethod = "findDateRangeParam"; + return Lists.newArrayList(); + } + + } + + public class StringStrengthsWithOptionalResourceProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Search() + public List findString( + @OptionalParam(name = Patient.SP_NAME) String theDate) { + myLastMethod = "findString"; + return Lists.newArrayList(); + } + + @Search() + public List findStringAndListParam( + @OptionalParam(name = Patient.SP_NAME) StringAndListParam theRange) { + myLastMethod = "findStringAndListParam"; + return Lists.newArrayList(); + } + + @Search + public List find() { + myLastMethod = "find"; + return Lists.newArrayList(); + } + + } + + + public class StringStrengthsWithOptionalResourceProviderReverseOrder implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Search() + public List findA( + @OptionalParam(name = Patient.SP_NAME) String theDate) { + myLastMethod = "findString"; + return Lists.newArrayList(); + } + + @Search + public List findB() { + myLastMethod = "find"; + return Lists.newArrayList(); + } + + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java index 6fd38e3a591..1d77a7d16d6 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java @@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.SearchStyleEnum; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.gclient.StringClientParam; @@ -21,7 +22,6 @@ import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -66,7 +66,16 @@ public class SearchR4Test { ourIdentifiers = null; } - private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException { + private Bundle executeSearchAndValidateHasLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException { + Bundle bundle = executeSearch(httpGet, theExpectEncoding); + String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertNotNull(linkNext); + + assertEquals(10, bundle.getEntry().size()); + return bundle; + } + + private Bundle executeSearch(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException { Bundle bundle; try (CloseableHttpResponse status = ourClient.execute(httpGet)) { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); @@ -76,9 +85,6 @@ public class SearchR4Test { assertEquals(theExpectEncoding, ct); bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent); validate(bundle); - assertEquals(10, bundle.getEntry().size()); - String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertNotNull(linkNext); } return bundle; } @@ -98,6 +104,21 @@ public class SearchR4Test { } + /** + * See #1763 + */ + @Test + public void testSummaryCount() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&"+Constants.PARAM_SUMMARY + "=" + SummaryEnum.COUNT.getCode()); + Bundle bundle = executeSearch(httpGet, EncodingEnum.JSON); + ourLog.info(toJson(bundle)); + assertEquals(200, bundle.getTotal()); + assertEquals("searchset", bundle.getType().toCode()); + assertEquals(0, bundle.getEntry().size()); + } + + + @Test public void testPagingPreservesElements() throws Exception { HttpGet httpGet; @@ -107,7 +128,7 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); assertThat(toJson(bundle), not(containsString("\"active\""))); linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl(); assertThat(linkSelf, containsString("_elements=name")); @@ -118,7 +139,7 @@ public class SearchR4Test { // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); assertThat(toJson(bundle), not(containsString("\"active\""))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_elements=name")); @@ -126,7 +147,7 @@ public class SearchR4Test { // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); assertThat(toJson(bundle), not(containsString("\"active\""))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_elements=name")); @@ -134,7 +155,7 @@ public class SearchR4Test { // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); assertThat(toJson(bundle), not(containsString("\"active\""))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_elements=name")); @@ -203,25 +224,25 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); @@ -235,26 +256,26 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); assertThat(toJson(bundle), containsString("active")); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=json")); @@ -268,25 +289,25 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); @@ -301,28 +322,28 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar"); httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); // Fetch the next page httpGet = new HttpGet(linkNext); httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, not(containsString("_format"))); @@ -336,25 +357,25 @@ public class SearchR4Test { // Initial search httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=xml")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=xml")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=xml")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); assertThat(linkNext, containsString("_format=xml")); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java index 16c3506a56c..7c24b49ab04 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; @@ -18,9 +19,12 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.hamcrest.CoreMatchers; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -32,14 +36,13 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; -import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import ca.uhn.fhir.test.utilities.JettyUtil; - public class SummaryParamR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class); @@ -223,7 +226,7 @@ public class SummaryParamR4Test { assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString(">TEXT<"))); assertThat(responseContent, (containsString("Medication/123"))); - assertThat(responseContent, not(CoreMatchers.containsStringIgnoringCase("note"))); + assertThat(responseContent, not(containsStringIgnoringCase("note"))); } ); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java index 20be8fee5bc..ab8ee8a1838 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java @@ -35,12 +35,16 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.Invocation; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.*; @@ -201,12 +205,13 @@ public class ConsentInterceptorTest { assertThat(responseContent, containsString("A DIAG")); } - verify(myConsentSvc, times(1)).startOperation(any(), any()); - verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); - verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); - verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); - verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(1)).startOperation(any(), any()); + verify(myConsentSvc, timeout(10000).times(2)).canSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(3)).willSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(10000).times(1)).completeOperationSuccess(any(), any()); + verify(myConsentSvc, timeout(10000).times(0)).completeOperationFailure(any(), any(), any()); verifyNoMoreInteractions(myConsentSvc); + } @Test @@ -241,6 +246,7 @@ public class ConsentInterceptorTest { ourPatientProvider.store((Patient) new Patient().setActive(true).setId("PTA")); ourPatientProvider.store((Patient) new Patient().setActive(false).setId("PTB")); + reset(myConsentSvc); when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED); when(myConsentSvc.willSeeResource(any(RequestDetails.class), any(IBaseResource.class), any())).thenAnswer(t->{ @@ -266,10 +272,10 @@ public class ConsentInterceptorTest { assertEquals("PTB", response.getEntry().get(1).getResource().getIdElement().getIdPart()); } - verify(myConsentSvc, times(1)).startOperation(any(), any()); - verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any()); - verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); - verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any()); + verify(myConsentSvc, timeout(1000).times(1)).startOperation(any(), any()); + verify(myConsentSvc, timeout(1000).times(2)).canSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(1000).times(3)).willSeeResource(any(), any(), any()); + verify(myConsentSvc, timeout(1000).times(1)).completeOperationSuccess(any(), any()); verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any()); verifyNoMoreInteractions(myConsentSvc); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java new file mode 100644 index 00000000000..68bc9ad406d --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java @@ -0,0 +1,170 @@ +package ca.uhn.fhir.rest.server.interceptor; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.test.utilities.HttpClientRule; +import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import ca.uhn.fhir.util.UrlUtil; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class FhirPathFilterInterceptorTest { + + private static final Logger ourLog = LoggerFactory.getLogger(FhirPathFilterInterceptorTest.class); + @ClassRule + public static HttpClientRule ourClientRule = new HttpClientRule(); + private static FhirContext ourCtx = FhirContext.forR4(); + @ClassRule + public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx); + @ClassRule + public static HashMapResourceProviderRule ourProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class); + private IGenericClient myClient; + private String myBaseUrl; + private CloseableHttpClient myHttpClient; + private IIdType myPatientId; + + @Before + public void before() { + ourProviderRule.clear(); + ourServerRule.getRestfulServer().getInterceptorService().unregisterAllInterceptors(); + ourServerRule.getRestfulServer().getInterceptorService().registerInterceptor(new FhirPathFilterInterceptor()); + + myClient = ourServerRule.getFhirClient(); + myBaseUrl = "http://localhost:" + ourServerRule.getPort(); + myHttpClient = ourClientRule.getClient(); + } + + @Test + public void testUnfilteredResponse() throws IOException { + createPatient(); + + HttpGet request = new HttpGet(myPatientId.getValue()); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString("\"system\": \"http://identifiers/1\"")); + assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]")); + } + } + + + @Test + public void testUnfilteredResponse_WithResponseHighlightingInterceptor() throws IOException { + ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor()); + createPatient(); + + HttpGet request = new HttpGet(myPatientId.getValue() + "?_format=" + Constants.FORMATS_HTML_JSON); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString(""system": "http://identifiers/1"")); + assertThat(responseText, containsString(""given": [ "Homer", "Jay" ]")); + } + } + + @Test + public void testFilteredResponse() throws IOException { + createPatient(); + + HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_pretty=true"); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString("\"system\": \"http://identifiers/1\"")); + assertThat(responseText, not(containsString("\"given\": [ \"Homer\", \"Jay\" ]"))); + } + + } + + @Test + public void testFilteredResponse_ExpressionReturnsResource() throws IOException { + createPatient(); + + HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient&_pretty=true"); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString("\"resource\": {")); + assertThat(responseText, containsString("\"system\": \"http://identifiers/1\"")); + assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]")); + } + + } + + @Test + public void testFilteredResponse_ExpressionIsInvalid() throws IOException { + createPatient(); + + HttpGet request = new HttpGet(myPatientId + "?_fhirpath=" + UrlUtil.escapeUrlParam("***")); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertEquals(400, response.getStatusLine().getStatusCode()); + assertThat(responseText, containsString("Error parsing FHIRPath expression: Error performing *: left operand has more than one value")); + } + + } + + @Test + public void testFilteredResponseBundle() throws IOException { + createPatient(); + + HttpGet request = new HttpGet(myBaseUrl + "/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true"); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString( + " \"valueHumanName\": {\n" + + " \"family\": \"Simpson\",\n" + + " \"given\": [ \"Homer\", \"Jay\" ]\n" + + " }" + )); + } + + } + + @Test + public void testFilteredResponse_WithResponseHighlightingInterceptor() throws IOException { + ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor()); + createPatient(); + + HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_format=" + Constants.FORMATS_HTML_JSON); + try (CloseableHttpResponse response = myHttpClient.execute(request)) { + String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response:\n{}", responseText); + assertThat(responseText, containsString(""system": "http://identifiers/1"")); + assertThat(responseText, not(containsString(""given": [ "Homer", "Jay" ]"))); + } + + } + + private void createPatient() { + Patient p = new Patient(); + p.setActive(true); + p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1"); + p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2"); + p.addName().setFamily("Simpson").addGiven("Homer").addGiven("Jay"); + p.addName().setFamily("Simpson").addGiven("Grandpa"); + myPatientId = myClient.create().resource(p).execute().getId().withServerBase(myBaseUrl, "Patient"); + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java index 25a9e5177ca..72bfe7f35fc 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorThrowingExceptionR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; @@ -21,7 +22,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Resource; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -30,11 +35,12 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class InterceptorThrowingExceptionR4Test { @@ -57,7 +63,7 @@ public class InterceptorThrowingExceptionR4Test { } @After - public void after(){ + public void after() { ourServlet.getInterceptorService().unregisterAllInterceptors(); } @@ -84,26 +90,26 @@ public class InterceptorThrowingExceptionR4Test { @Test public void testFailureInProcessingCompletedNormally() throws Exception { - final List hit = new ArrayList<>(); + final List hit = Collections.synchronizedList(new ArrayList<>()); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(1); - throw new NullPointerException(); + throw new NullPointerException("Hit 1"); } }); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(2); - throw new NullPointerException(); + throw new NullPointerException("Hit 2"); } }); ourServlet.getInterceptorService().registerInterceptor(new InterceptorAdapter() { @Override public void processingCompletedNormally(ServletRequestDetails theRequestDetails) { hit.add(3); - throw new NullPointerException(); + throw new NullPointerException("Hit 3"); } }); @@ -119,6 +125,9 @@ public class InterceptorThrowingExceptionR4Test { assertEquals(200, status.getStatusLine().getStatusCode()); assertThat(response, containsString("FAM")); assertTrue(ourHitMethod); + + await().until(() -> hit.size() == 3); + ourLog.info("Hit: {}", hit); assertThat("Hits: " + hit.toString(), hit, contains(1, 2, 3)); @@ -165,7 +174,7 @@ public class InterceptorThrowingExceptionR4Test { proxyHandler.addServletWithMapping(servletHolder, "/*"); ourServer.setHandler(proxyHandler); JettyUtil.startServer(ourServer); - ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourPort = JettyUtil.getPortForStartedServer(ourServer); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); HttpClientBuilder builder = HttpClientBuilder.create(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java new file mode 100644 index 00000000000..e87a80303e5 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java @@ -0,0 +1,105 @@ +package ca.uhn.fhir.rest.server.interceptor; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import ca.uhn.test.concurrency.PointcutLatch; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.waitAtMost; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class ResponseSizeCapturingInterceptorTest { + + private static FhirContext ourCtx = FhirContext.forR4(); + @ClassRule + public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx); + private ResponseSizeCapturingInterceptor myInterceptor; + @Rule + public HashMapResourceProviderRule myPatientProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class); + @Mock + private Consumer myConsumer; + @Captor + private ArgumentCaptor myResultCaptor; + + @Before + public void before() { + myInterceptor = new ResponseSizeCapturingInterceptor(); + ourServerRule.getRestfulServer().registerInterceptor(myInterceptor); + } + + @After + public void after() { + ourServerRule.getRestfulServer().unregisterInterceptor(myInterceptor); + } + + @Test + public void testReadResource() throws InterruptedException { + PointcutLatch createLatch = new PointcutLatch(Pointcut.SERVER_PROCESSING_COMPLETED); + createLatch.setExpectedCount(1); + ourServerRule.getRestfulServer().getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_PROCESSING_COMPLETED, createLatch); + + Patient resource = new Patient(); + resource.setActive(true); + IIdType id = ourServerRule.getFhirClient().create().resource(resource).execute().getId().toUnqualifiedVersionless(); + + createLatch.awaitExpected(); + ourServerRule.getRestfulServer().getInterceptorService().unregisterInterceptor(createLatch); + + myInterceptor.registerConsumer(myConsumer); + + List stacks = Collections.synchronizedList(new ArrayList<>()); + doAnswer(t->{ + ResponseSizeCapturingInterceptor.Result result =t.getArgument(0, ResponseSizeCapturingInterceptor.Result.class); + try { + throw new Exception(); + } catch (Exception e) { + stacks.add("INVOCATION\n" + result.getRequestDetails().getCompleteUrl() + "\n" + ExceptionUtils.getStackTrace(e)); + } + return null; + }).when(myConsumer).accept(any()); + + resource = ourServerRule.getFhirClient().read().resource(Patient.class).withId(id).execute(); + assertEquals(true, resource.getActive()); + + verify(myConsumer, timeout(Duration.ofSeconds(10)).times(1)).accept(myResultCaptor.capture()); + assertEquals(100, myResultCaptor.getValue().getWrittenChars()); + } + + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProviderTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProviderTest.java index 220b0094db3..d63632d313c 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProviderTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProviderTest.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.rest.server.provider; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.gclient.IDeleteTyped; @@ -8,6 +10,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -21,6 +24,10 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,11 +35,17 @@ import javax.servlet.ServletException; import java.util.List; import java.util.stream.Collectors; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +@RunWith(MockitoJUnitRunner.class) public class HashMapResourceProviderTest { private static final Logger ourLog = LoggerFactory.getLogger(HashMapResourceProviderTest.class); @@ -43,6 +56,9 @@ public class HashMapResourceProviderTest { private static HashMapResourceProvider myPatientResourceProvider; private static HashMapResourceProvider myObservationResourceProvider; + @Mock + private IAnonymousInterceptor myAnonymousInterceptor; + @Before public void before() { ourRestServer.clearData(); @@ -52,6 +68,9 @@ public class HashMapResourceProviderTest { @Test public void testCreateAndRead() { + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, myAnonymousInterceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, myAnonymousInterceptor); + // Create Patient p = new Patient(); p.setActive(true); @@ -59,6 +78,9 @@ public class HashMapResourceProviderTest { assertThat(id.getIdPart(), matchesPattern("[0-9]+")); assertEquals("1", id.getVersionIdPart()); + verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED), any()); + verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED), any()); + // Read p = (Patient) ourClient.read().resource("Patient").withId(id).execute(); assertEquals(true, p.getActive()); @@ -282,6 +304,9 @@ public class HashMapResourceProviderTest { assertEquals("1", id.getVersionIdPart()); // Update + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, myAnonymousInterceptor); + ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, myAnonymousInterceptor); + p = new Patient(); p.setId(id); p.setActive(false); @@ -289,6 +314,9 @@ public class HashMapResourceProviderTest { assertThat(id.getIdPart(), matchesPattern("[0-9]+")); assertEquals("2", id.getVersionIdPart()); + verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any()); + verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED), any()); + assertEquals(1, myPatientResourceProvider.getCountCreate()); assertEquals(1, myPatientResourceProvider.getCountUpdate()); @@ -305,33 +333,6 @@ public class HashMapResourceProviderTest { } } - @AfterClass - public static void afterClassClearContext() throws Exception { - JettyUtil.closeServer(ourListenerServer); - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void startListenerServer() throws Exception { - ourRestServer = new MyRestfulServer(); - - ourListenerServer = new Server(0); - - ServletContextHandler proxyHandler = new ServletContextHandler(); - proxyHandler.setContextPath("/"); - - ServletHolder servletHolder = new ServletHolder(); - servletHolder.setServlet(ourRestServer); - proxyHandler.addServlet(servletHolder, "/*"); - - ourListenerServer.setHandler(proxyHandler); - JettyUtil.startServer(ourListenerServer); - int ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer); - String ourBase = "http://localhost:" + ourListenerPort + "/"; - ourCtx.getRestfulClientFactory().setSocketTimeout(120000); - ourClient = ourCtx.newRestfulGenericClient(ourBase); - } - private static class MyRestfulServer extends RestfulServer { MyRestfulServer() { @@ -359,5 +360,32 @@ public class HashMapResourceProviderTest { } + @AfterClass + public static void afterClassClearContext() throws Exception { + JettyUtil.closeServer(ourListenerServer); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void startListenerServer() throws Exception { + ourRestServer = new MyRestfulServer(); + + ourListenerServer = new Server(0); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(ourRestServer); + proxyHandler.addServlet(servletHolder, "/*"); + + ourListenerServer.setHandler(proxyHandler); + JettyUtil.startServer(ourListenerServer); + int ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer); + String ourBase = "http://localhost:" + ourListenerPort + "/"; + ourCtx.getRestfulClientFactory().setSocketTimeout(120000); + ourClient = ourCtx.newRestfulGenericClient(ourBase); + } + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/GraphQLEngineTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/GraphQLEngineTest.java index 70cd2b7511f..dd1a03f8276 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/GraphQLEngineTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/GraphQLEngineTest.java @@ -2,11 +2,18 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.utils.GraphQLEngine; -import org.hl7.fhir.utilities.graphql.*; +import org.hl7.fhir.utilities.graphql.EGraphEngine; +import org.hl7.fhir.utilities.graphql.EGraphQLException; +import org.hl7.fhir.utilities.graphql.GraphQLResponse; +import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; +import org.hl7.fhir.utilities.graphql.Parser; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -122,7 +129,7 @@ public class GraphQLEngineTest { @BeforeClass public static void beforeClass() { ourCtx = FhirContext.forR4(); - ourWorkerCtx = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport()); + ourWorkerCtx = new HapiWorkerContext(ourCtx, ourCtx.getValidationSupport()); } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java index d72f946e6c4..bcb4cceb339 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java @@ -4,13 +4,16 @@ import ca.uhn.fhir.context.FhirContext; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; import org.junit.Test; import java.util.List; +import java.util.Optional; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class ParametersUtilR4Test { @@ -47,4 +50,16 @@ public class ParametersUtilR4Test { MatcherAssert.assertThat(values, Matchers.contains("VALUE1", "VALUE2")); } + @Test + public void testGetValueAsInteger(){ + Parameters p = new Parameters(); + p.addParameter() + .setName("foo") + .setValue(new IntegerType(123)); + + Optional value = ParametersUtil.getNamedParameterValueAsInteger(FhirContext.forR4(), p, "foo"); + assertTrue(value.isPresent()); + assertEquals(123, value.get().intValue()); + } + } diff --git a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java index 817a3246f55..4256d38806a 100644 --- a/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java +++ b/hapi-fhir-structures-r4/src/test/java/org/hl7/fhir/r4/elementmodel/PropertyTest.java @@ -1,24 +1,21 @@ package org.hl7.fhir.r4.elementmodel; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4.model.ElementDefinition; import org.hl7.fhir.r4.model.StructureDefinition; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * Created by axemj on 14/07/2017. @@ -62,6 +59,6 @@ public class PropertyTest { final String sdString = IOUtils.toString(PropertyTest.class.getResourceAsStream("/customPatientSd.xml"), StandardCharsets.UTF_8); final IParser parser = ourCtx.newXmlParser(); sd = parser.parseResource(StructureDefinition.class, sdString); - workerContext = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport()); + workerContext = new HapiWorkerContext(ourCtx, ourCtx.getValidationSupport()); } } diff --git a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json new file mode 100644 index 00000000000..e0a15fc763a --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json @@ -0,0 +1,260 @@ +{ + "resourceType": "Bundle", + "meta": { + "profile": [ + "http://forcare.com/fhir/createCda" + ] + }, + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "DocumentReference", + "id": "doc1", + "contained": [ + { + "resourceType": "Patient", + "id": "patient", + "identifier": [ + { + "system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7", + "value": "12346" + } + ], + "name": [ + { + "use": "official", + "family": "Beugels", + "given": [ + "Kees" + ] + } + ], + "gender": "male", + "birthDate": "1970-01-01" + } + ], + "status": "current", + "type": { + "coding": [ + { + "system": "2.16.840.1.113883.6.1", + "code": "57016-8", + "display": "Privacy Policy Acknowledgment" + } + ] + }, + "category": [ + { + "coding": [ + { + "system": "2.16.840.1.113883.6.1", + "code": "57016-8", + "display": "Privacy Policy Acknowledgment" + } + ] + } + ], + "subject": { + "reference": "#patient" + }, + "date": "2016-05-04T08:18:03.203Z", + "author": [ + { + "reference": "#patient" + } + ], + "description": "Hospital Privacy Consent", + "securityLabel": [ + { + "coding": [ + { + "system": "2.16.840.1.113883.5.25", + "code": "N", + "display": "Normal" + } + ] + } + ], + "content": [ + { + "attachment": { + "contentType": "text/xml", + "language": "en-US", + "url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5" + }, + "format": { + "system": "1.3.6.1.4.1.19376.1.2.3", + "code": "urn:ihe:iti:bppc-sd:2007", + "display": "Basic Patient Privacy Consent (scanned part)" + } + } + ], + "context": { + "event": [ + { + "coding": [ + { + "system": "1.2.826.0.1.3680043.2.1611.2.10", + "code": "allowMedicalDoctorsFromHospitalAToSeeDocuments", + "display": "I allow Medical Doctors in Hospital A to access my medical record" + } + ] + }, + { + "coding": [ + { + "system": "1.2.826.0.1.3680043.2.1611.2.10", + "code": "denyMedicalDoctorsFromHospitalBToSeeDocuments", + "display": "I deny Medical Doctors in Hospital B to access my medical record" + } + ] + } + ], + "period": { + "start": "2016-05-04T08:18:03.203Z", + "end": "2016-09-04T08:18:03.203Z" + }, + "facilityType": { + "coding": [ + { + "system": "2.16.840.1.113883.5.10588", + "code": "HOSP", + "display": "Hospital" + } + ] + }, + "practiceSetting": { + "coding": [ + { + "system": "2.16.840.1.113883.2.1.6.8", + "code": "300", + "display": "General Medicine" + } + ] + } + } + } + }, + { + "resource": { + "resourceType": "DocumentReference", + "id": "doc2", + "contained": [ + { + "resourceType": "Patient", + "id": "patient", + "identifier": [ + { + "system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7", + "value": "12345" + } + ], + "name": [ + { + "use": "official", + "family": "Baker", + "given": [ + "Rob" + ] + } + ], + "gender": "male", + "birthDate": "1970-01-01" + } + ], + "status": "current", + "type": { + "coding": [ + { + "system": "2.16.840.1.113883.6.1", + "code": "57016-8", + "display": "Privacy Policy Acknowledgment" + } + ] + }, + "category": [ + { + "coding": [ + { + "system": "2.16.840.1.113883.6.1", + "code": "57016-8", + "display": "Privacy Policy Acknowledgment" + } + ] + } + ], + "subject": { + "reference": "#patient" + }, + "date": "2016-05-04T08:18:03.203Z", + "author": [ + { + "reference": "#patient" + } + ], + "description": "GPs Privacy Consent", + "securityLabel": [ + { + "coding": [ + { + "system": "2.16.840.1.113883.5.25", + "code": "N", + "display": "Normal" + } + ] + } + ], + "content": [ + { + "attachment": { + "contentType": "text/xml", + "language": "en-US", + "url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5" + }, + "format": { + "system": "1.3.6.1.4.1.19376.1.2.3", + "code": "urn:ihe:iti:bppc-sd:2007", + "display": "Basic Patient Privacy Consent (scanned part)" + } + } + ], + "context": { + "event": [ + { + "coding": [ + { + "system": "1.2.826.0.1.3680043.2.1611.2.10", + "code": "denyGeneralPractitionersFromHestiaToSeeDocuments", + "display": "I deny Medical Doctors in Hestia General Practitioners to access my medical record" + } + ] + } + ], + "period": { + "start": "2016-05-04T08:18:03.203Z", + "end": "2016-09-04T08:18:03.203Z" + }, + "facilityType": { + "coding": [ + { + "system": "2.16.840.1.113883.5.10588", + "code": "HOSP", + "display": "Hospital" + } + ] + }, + "practiceSetting": { + "coding": [ + { + "system": "2.16.840.1.113883.2.1.6.8", + "code": "300", + "display": "General Medicine" + } + ] + } + } + } + } + ] +} diff --git a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml new file mode 100644 index 00000000000..b10ad761ced --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index ffd8da62120..f5d1094edaa 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -116,6 +116,11 @@ 1.4 true + + com.google.code.gson + gson + true + diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/DefaultProfileValidationSupport.java deleted file mode 100644 index 10848c810f0..00000000000 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/DefaultProfileValidationSupport.java +++ /dev/null @@ -1,351 +0,0 @@ -package org.hl7.fhir.r5.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.*; -import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.*; - -public class DefaultProfileValidationSupport implements IValidationSupport { - - private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/"; - private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/"; - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class); - - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; - - private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set theWantCodes, List theConcepts) { - for (ConceptDefinitionComponent next : theConcepts) { - if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) { - theRetVal - .addContains() - .setSystem(theInclude.getSystem()) - .setCode(next.getCode()) - .setDisplay(next.getDisplay()); - } - addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept()); - } - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet()); - - Set wantCodes = new HashSet<>(); - for (ConceptReferenceComponent next : theInclude.getConcept()) { - wantCodes.add(next.getCode()); - } - - CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); - if (system != null) { - List concepts = system.getConcept(); - addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts); - } - - for (UriType next : theInclude.getValueSet()) { - ValueSet vs = myValueSets.get(defaultString(next.getValueAsString())); - if (vs != null) { - for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) { - ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude); - retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains()); - } - } - } - - return retVal; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - retVal.addAll(myCodeSystems.values()); - retVal.addAll(myStructureDefinitions.values()); - retVal.addAll(myValueSets.values()); - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList<>(provideStructureDefinitionMap(theContext).values()); - } - - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); - } - - private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { - synchronized (this) { - Map codeSystems = myCodeSystems; - Map valueSets = myValueSets; - if (codeSystems == null || valueSets == null) { - codeSystems = new HashMap<>(); - valueSets = new HashMap<>(); - - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/valuesets.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v2-tables.xml"); - loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v3-codesystems.xml"); - - myCodeSystems = codeSystems; - myValueSets = valueSets; - } - - // System can take the form "http://url|version" - String system = theSystem; - if (system.contains("|")) { - String version = system.substring(system.indexOf('|') + 1); - if (version.matches("^[0-9.]+$")) { - system = system.substring(0, system.indexOf('|')); - } - } - - if (codeSystem) { - return codeSystems.get(system); - } else { - return valueSets.get(system); - } - } - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - Validate.notBlank(theUri, "theUri must not be null or blank"); - - if (theClass.equals(StructureDefinition.class)) { - return (T) fetchStructureDefinition(theContext, theUri); - } - - if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { - return (T) fetchValueSet(theContext, theUri); - } - - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { - String url = theUrl; - if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { - // no change - } else if (url.indexOf('/') == -1) { - url = URL_PREFIX_STRUCTURE_DEFINITION + url; - } else if (StringUtils.countMatches(url, '/') == 1) { - url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; - } - return provideStructureDefinitionMap(theContext).get(url); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); - } - - public void flush() { - myCodeSystems = null; - myStructureDefinitions = null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - if (isBlank(theSystem) || Constants.codeSystemNotNeeded(theSystem)) { - return false; - } - CodeSystem cs = fetchCodeSystem(theContext, theSystem); - return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - private void loadCodeSystems(FhirContext theContext, Map theCodeSystems, Map theValueSets, String theClasspath) { - ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); - InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - InputStreamReader reader = null; - if (inputStream != null) { - try { - reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof CodeSystem) { - CodeSystem nextValueSet = (CodeSystem) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextValueSet); - } - } else if (next.getResource() instanceof ValueSet) { - ValueSet nextValueSet = (ValueSet) next.getResource(); - nextValueSet.getText().setDivAsString(""); - String system = nextValueSet.getUrl(); - if (isNotBlank(system)) { - theValueSets.put(system, nextValueSet); - } - } - } - } finally { - try { - if (reader != null) { - reader.close(); - } - inputStream.close(); - } catch (IOException e) { - ourLog.warn("Failure closing stream", e); - } - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private void loadStructureDefinitions(FhirContext theContext, Map theCodeSystems, String theClasspath) { - ourLog.info("Loading structure definitions from classpath: {}", theClasspath); - InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); - if (valuesetText != null) { - InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8); - - Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - if (next.getResource() instanceof StructureDefinition) { - StructureDefinition nextSd = (StructureDefinition) next.getResource(); - nextSd.getText().setDivAsString(""); - String system = nextSd.getUrl(); - if (isNotBlank(system)) { - theCodeSystems.put(system, nextSd); - } - } - } - } else { - ourLog.warn("Unable to load resource: {}", theClasspath); - } - } - - private Map provideStructureDefinitionMap(FhirContext theContext) { - Map structureDefinitions = myStructureDefinitions; - if (structureDefinitions == null) { - structureDefinitions = new HashMap<>(); - - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-resources.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-types.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-others.xml"); - loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/extension/extension-definitions.xml"); - - myStructureDefinitions = structureDefinitions; - } - return structureDefinitions; - } - - private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List conceptList, boolean theCaseSensitive) { - String code = theCode; - if (theCaseSensitive == false) { - code = code.toUpperCase(); - } - - return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code); - } - - private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List conceptList, boolean theCaseSensitive, String code) { - CodeValidationResult retVal = null; - for (ConceptDefinitionComponent next : conceptList) { - String nextCandidate = next.getCode(); - if (theCaseSensitive == false) { - nextCandidate = nextCandidate.toUpperCase(); - } - if (nextCandidate.equals(code)) { - retVal = new CodeValidationResult(null, null, next, next.getDisplay()); - break; - } - - // recurse - retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive); - if (retVal != null) { - break; - } - } - - if (retVal != null) { - retVal.setCodeSystemName(theCodeSystem.getName()); - retVal.setCodeSystemVersion(theCodeSystem.getVersion()); - } - - return retVal; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - if (isNotBlank(theValueSetUrl)) { - ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this)); - try { - ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl); - if (valueSet != null) { - ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null); - Optional haveMatch = expanded - .getValueset() - .getExpansion() - .getContains() - .stream() - .filter(t -> (Constants.codeSystemNotNeeded(theCodeSystem) || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode)) - .findFirst(); - if (haveMatch.isPresent()) { - return new CodeValidationResult(new ConceptDefinitionComponent(theCode)); - } - } - } catch (Exception e) { - return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage()); - } - - return null; - } - - if (theCodeSystem != null) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - - @Override - public IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return validateCode(theContext, theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); - } - -} diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java index c9cad2b48c5..c9e8747dd13 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java @@ -24,17 +24,24 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; import ca.uhn.fhir.util.ReflectionUtil; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r5.hapi.fhirpath.FhirPathR5; import org.hl7.fhir.r5.hapi.rest.server.R5BundleFactory; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.Reference; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.StructureDefinition; import java.io.InputStream; import java.util.Date; @@ -45,15 +52,10 @@ public class FhirR5 implements IFhirVersion { private String myId; @Override - public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) { + public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) { return new FhirPathR5(theFhirContext); } - @Override - public IContextValidationSupport createValidationSupport() { - return ReflectionUtil.newInstanceOfFhirProfileValidationSupport("org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport"); - } - @Override public IBaseResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase) { StructureDefinition retVal = new StructureDefinition(); diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java index c88ada2d294..38a9e8a2560 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java @@ -1,10 +1,9 @@ package org.hl7.fhir.r5.hapi.ctx; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.CoverageIgnore; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -21,23 +20,23 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.terminologies.ValueSetExpanderFactory; -import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple; import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.utilities.TranslationServices; +import org.hl7.fhir.utilities.i18n.I18nBase; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationOptions; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory { +public final class HapiWorkerContext extends I18nBase implements IWorkerContext { private final FhirContext myCtx; private final Cache myFetchedResourceCache; private IValidationSupport myValidationSupport; @@ -56,11 +55,14 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build(); + + // Set a default locale + setValidationMessageLanguage(getLocale()); } @Override public List allStructures() { - return myValidationSupport.fetchAllStructureDefinitions(myCtx); + return myValidationSupport.fetchAllStructureDefinitions(); } @Override @@ -73,7 +75,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return null; } else { - return myValidationSupport.fetchCodeSystem(myCtx, theSystem); + return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem); } } @@ -87,13 +89,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander throw new UnsupportedOperationException(); } - @Override - public ValueSetExpander getExpander() { - ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this); - retVal.setMaxExpansionSize(Integer.MAX_VALUE); - return retVal; - } - @Override public org.hl7.fhir.r5.utils.INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) { throw new UnsupportedOperationException(); @@ -144,7 +139,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander if (myValidationSupport == null) { return false; } else { - return myValidationSupport.isCodeSystemSupported(myCtx, theSystem); + return myValidationSupport.isCodeSystemSupported(myValidationSupport, theSystem); } } @@ -171,41 +166,26 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay) { - IContextValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, null); + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, null); if (result == null) { return null; } - return new ValidationResult((IssueSeverity) result.getSeverity(), result.getMessage(), (ConceptDefinitionComponent) result.asConceptDefinition()); + IssueSeverity severity = null; + if (result.getSeverity() != null) { + severity = IssueSeverity.fromCode(result.getSeverityCode()); + } + ConceptDefinitionComponent definition = new ConceptDefinitionComponent().setCode(result.getCode()); + return new ValidationResult(severity, result.getMessage(), definition); } @Override public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay, ValueSet theVs) { - /* - * The following valueset is a special case, since the BCP codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } - - /* - * The following valueset is a special case, since the mime types codesystem is very difficult to expand - */ - if ("http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) { - ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); - definition.setCode(theSystem); - definition.setDisplay(theCode); - return new ValidationResult(definition); - } - IValidationSupport.CodeValidationResult outcome; if (isNotBlank(theVs.getUrl())) { - outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl()); + outcome = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs.getUrl()); } else { - outcome = myValidationSupport.validateCodeInValueSet(myCtx, theSystem, theCode, theDisplay, theVs); + outcome = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs); } if (outcome != null && outcome.isOk()) { @@ -218,7 +198,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + Constants.codeSystemWithDefaultDescription(theSystem) + "]"); } - @Override public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet vs) { return validateCode(theOptions, null, code, null, vs); @@ -232,7 +211,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander @Override public void generateSnapshot(StructureDefinition p) throws FHIRException { - + myValidationSupport.generateSnapshot(myValidationSupport, p, "", "", ""); } @Override @@ -257,30 +236,26 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValueSetExpansionOutcome expand(ValueSet theSource, Parameters theProfile) { - ValueSetExpansionOutcome vso; - try { - vso = getExpander().expand(theSource, theProfile); - } catch (InvalidRequestException e) { - throw e; - } catch (Exception e) { - throw new InternalErrorException(e); - } - if (vso.getError() != null) { - throw new InvalidRequestException(vso.getError()); - } else { - return vso; - } - } - - @Override - public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) { + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHierarchical) { throw new UnsupportedOperationException(); } @Override - public ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException { - return myValidationSupport.expandValueSet(myCtx, theInc); + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHierarchical) throws TerminologyServiceException { + ValueSet input = new ValueSet(); + input.getCompose().addInclude(theInc); + IValidationSupport.ValueSetExpansionOutcome output = myValidationSupport.expandValueSet(myValidationSupport, null, input); + return new ValueSetExpander.ValueSetExpansionOutcome((ValueSet) output.getValueSet(), output.getError(), null); + } + + @Override + public Locale getLocale() { + return Locale.getDefault(); + } + + @Override + public void setLocale(Locale locale) { + // ignore } @Override @@ -359,7 +334,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander return null; } else { @SuppressWarnings("unchecked") - T retVal = (T) myFetchedResourceCache.get(theUri, t -> myValidationSupport.fetchResource(myCtx, theClass, theUri)); + T retVal = (T) myFetchedResourceCache.get(theUri, t -> myValidationSupport.fetchResource(theClass, theUri)); return retVal; } } @@ -394,7 +369,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander } @Override - public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException { + public ValueSetExpander.ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHierarchical) throws FHIRException { throw new UnsupportedOperationException(); } @@ -408,4 +383,12 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander return null; } + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; + } + } diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/IValidationSupport.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/IValidationSupport.java deleted file mode 100644 index 745a1357f6d..00000000000 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/IValidationSupport.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.hl7.fhir.r5.hapi.ctx; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.util.List; - -public interface IValidationSupport - extends IContextValidationSupport { - - /** - * Expands the given portion of a ValueSet - * - * @param theInclude The portion to include - * @return The expansion - */ - @Override - ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude); - - /** - * Load and return all possible structure definitions - */ - @Override - List fetchAllStructureDefinitions(FhirContext theContext); - - /** - * Fetch a code system by Uri - * - * @param uri Canonical Uri of the code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String uri); - - /** - * Fetch a valueset by Uri - * - * @param uri Canonical Uri of the ValueSet - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - ValueSet fetchValueSet(FhirContext theContext, String uri); - - /** - * Loads a resource needed by the validation (a StructureDefinition, or a - * ValueSet) - * - * @param theContext The HAPI FHIR Context object current in use by the validator - * @param theClass The type of the resource to load - * @param theUri The resource URI - * @return Returns the resource, or null if no resource with the - * given URI can be found - */ - @Override - T fetchResource(FhirContext theContext, Class theClass, String theUri); - - @Override - StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl); - - /** - * Returns true if codes in the given code system can be expanded - * or validated - * - * @param theSystem The URI for the code system, e.g. "http://loinc.org" - * @return Returns true if codes in the given code system can be - * validated - */ - @Override - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); - - /** - * Generate a snapshot from the given differential profile. - * - * @param theInput - * @param theUrl - * @param theWebUrl - * @param theProfileName - * @return Returns null if this module does not know how to handle this request - */ - StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName); - -} diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java index 1d68df9bb84..d82be431bc1 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java @@ -1,27 +1,24 @@ package org.hl7.fhir.r5.hapi.fhirpath; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.fluentpath.FluentPathExecutionException; -import ca.uhn.fhir.fluentpath.IFluentPath; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.fhirpath.FhirPathExecutionException; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.utils.FHIRPathEngine; import java.util.List; import java.util.Optional; -public class FhirPathR5 implements IFluentPath { +public class FhirPathR5 implements IFhirPath { private FHIRPathEngine myEngine; public FhirPathR5(FhirContext theCtx) { - if (!(theCtx.getValidationSupport() instanceof IValidationSupport)) { - throw new IllegalStateException("Validation support module configured on context appears to be for the wrong FHIR version- Does not extend " + IValidationSupport.class.getName()); - } - IValidationSupport validationSupport = (IValidationSupport) theCtx.getValidationSupport(); + IValidationSupport validationSupport = theCtx.getValidationSupport(); myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport)); } @@ -32,12 +29,12 @@ public class FhirPathR5 implements IFluentPath { try { result = myEngine.evaluate((Base) theInput, thePath); } catch (FHIRException e) { - throw new FluentPathExecutionException(e); + throw new FhirPathExecutionException(e); } for (Base next : result) { if (!theReturnType.isAssignableFrom(next.getClass())) { - throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); + throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); } } diff --git a/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index e1ec9f95469..6b1f3932139 100644 --- a/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.client.impl.BaseClient; import ca.uhn.fhir.rest.client.impl.GenericClient; @@ -386,23 +387,81 @@ public class GenericClientTest { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123") - .withAdditionalHeader("myHeaderName", "myHeaderValue").execute(); + MethodOutcome outcome = client + .delete() + .resourceById("Patient", "123") + .withAdditionalHeader("myHeaderName", "myHeaderValue") + .execute(); + oo = (OperationOutcome) outcome.getOperationOutcome(); assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); assertEquals("DELETE", capt.getValue().getMethod()); - Assert.assertEquals("testDelete01", outcome.getIssueFirstRep().getLocation().get(0).getValue()); + Assert.assertEquals("testDelete01", oo.getIssueFirstRep().getLocation().get(0).getValue()); assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue()); + } + + + @Test + public void testDeleteInvalidResponse() throws Exception { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().addLocation("testDelete01"); + String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")}); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), StandardCharsets.UTF_8)); - outcome = (OperationOutcome) client.delete().resourceById(new IdType("Location", "123", "456")).prettyPrint().encodedJson().execute(); - assertEquals("http://example.com/fhir/Location/123?_pretty=true", capt.getAllValues().get(1).getURI().toString()); - assertEquals("DELETE", capt.getValue().getMethod()); + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - Assert.assertEquals(null, outcome); + // Try with invalid response + try { + client + .delete() + .resourceById(new IdType("Location", "123", "456")) + .prettyPrint() + .encodedJson() + .execute(); + } catch (FhirClientConnectionException e) { + assertEquals(0, e.getStatusCode()); + assertThat(e.getMessage(), containsString("Failed to parse response from server when performing DELETE to URL")); + } } + + + @Test + public void testDeleteNoResponse() throws Exception { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().addLocation("testDelete01"); + String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")}); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ooStr), StandardCharsets.UTF_8)); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + MethodOutcome outcome = client + .delete() + .resourceById("Patient", "123") + .withAdditionalHeader("myHeaderName", "myHeaderValue") + .execute(); + + oo = (OperationOutcome) outcome.getOperationOutcome(); + assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); + assertEquals("DELETE", capt.getValue().getMethod()); + Assert.assertEquals("testDelete01", oo.getIssueFirstRep().getLocation().get(0).getValue()); + assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue()); + + } + @Test public void testHistory() throws Exception { @@ -413,12 +472,8 @@ public class GenericClientTest { when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8); - } - }); + when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> + new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8)); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -428,7 +483,7 @@ public class GenericClientTest { response = client .history() .onServer() - .andReturnBundle(Bundle.class) + .returnBundle(Bundle.class) .withAdditionalHeader("myHeaderName", "myHeaderValue") .execute(); assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); @@ -439,7 +494,7 @@ public class GenericClientTest { response = client .history() .onType(Patient.class) - .andReturnBundle(Bundle.class) + .returnBundle(Bundle.class) .withAdditionalHeader("myHeaderName", "myHeaderValue1") .withAdditionalHeader("myHeaderName", "myHeaderValue2") .execute(); diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 6caefd3117b..614e36ef8fd 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -19,17 +19,17 @@ ca.uhn.hapi.fhir hapi-fhir-base - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-server - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-client - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java new file mode 100644 index 00000000000..4c834c1eff4 --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.test.utilities; + +/*- + * #%L + * HAPI FHIR Test Utilities + * %% + * Copyright (C) 2014 - 2020 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 org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class HttpClientRule implements TestRule { + private CloseableHttpClient myClient; + + @Override + public Statement apply(Statement theBase, Description theDescription) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + startClient(); + theBase.evaluate(); + stopClient(); + } + }; + } + + + private void stopClient() throws Exception { + myClient.close(); + } + + private void startClient() { + myClient = HttpClientBuilder + .create() + .build(); + } + + public CloseableHttpClient getClient() { + return myClient; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/ProxyUtil.java similarity index 55% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java rename to hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/ProxyUtil.java index cad255ac6b5..3069939a2e8 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/ProxyUtil.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.subscription.module.config; +package ca.uhn.fhir.test.utilities; /*- * #%L - * HAPI FHIR Subscription Server + * HAPI FHIR Test Utilities * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -20,10 +20,18 @@ package ca.uhn.fhir.jpa.subscription.module.config; * #L% */ -import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu2Config; -import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu3Config; -import org.springframework.context.annotation.Import; +import org.apache.commons.lang3.Validate; +import org.springframework.aop.framework.AopProxyUtils; + +public class ProxyUtil { + + @SuppressWarnings("unchecked") + public static T getSingletonTarget(Object theSource, Class theSubscriptionTriggeringSvcClass) { + Validate.notNull(theSource); + if (theSubscriptionTriggeringSvcClass.isAssignableFrom(theSource.getClass())) { + return (T) theSource; + } + return (T) AopProxyUtils.getSingletonTarget(theSource); + } -@Import({SearchParamDstu2Config.class}) -public class SubscriptionDstu2Config extends BaseSubscriptionConfig { } diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java index cfeb6487628..3922e8f0ab9 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java @@ -21,10 +21,12 @@ package ca.uhn.fhir.test.utilities.server; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.JettyUtil; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -43,35 +45,66 @@ import java.util.concurrent.TimeUnit; public class RestfulServerRule implements TestRule { private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerRule.class); - private final FhirContext myFhirContext; - private final Object[] myProviders; + private FhirContext myFhirContext; + private Object[] myProviders; + private FhirVersionEnum myFhirVersion; private Server myServer; private RestfulServer myServlet; private int myPort; private CloseableHttpClient myHttpClient; private IGenericClient myFhirClient; + /** + * Constructor + */ public RestfulServerRule(FhirContext theFhirContext, Object... theProviders) { + Validate.notNull(theFhirContext); myFhirContext = theFhirContext; myProviders = theProviders; } + /** + * Constructor: If this is used, it will create and tear down a FhirContext which is good for memory + */ + public RestfulServerRule(FhirVersionEnum theFhirVersionEnum) { + Validate.notNull(theFhirVersionEnum); + myFhirVersion = theFhirVersionEnum; + } + @Override public Statement apply(Statement theBase, Description theDescription) { return new Statement() { @Override public void evaluate() throws Throwable { + createContextIfNeeded(); startServer(); theBase.evaluate(); stopServer(); + destroyContextIfWeCreatedIt(); } }; } + private void createContextIfNeeded() { + if (myFhirVersion != null) { + myFhirContext = new FhirContext(myFhirVersion); + } + } + + private void destroyContextIfWeCreatedIt() { + if (myFhirVersion != null) { + myFhirContext = null; + } + } + private void stopServer() throws Exception { JettyUtil.closeServer(myServer); + myServer = null; + myFhirClient = null; + myHttpClient.close(); + myHttpClient = null; } private void startServer() throws Exception { @@ -80,7 +113,9 @@ public class RestfulServerRule implements TestRule { ServletHandler servletHandler = new ServletHandler(); myServlet = new RestfulServer(myFhirContext); myServlet.setDefaultPrettyPrint(true); - myServlet.registerProviders(myProviders); + if (myProviders != null) { + myServlet.registerProviders(myProviders); + } ServletHolder servletHolder = new ServletHolder(myServlet); servletHandler.addServletWithMapping(servletHolder, "/*"); diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java index 0c95bca015b..af4e01ebac0 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -43,45 +45,73 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { private static final int DEFAULT_TIMEOUT_SECONDS = 10; private static final FhirObjectPrinter ourFhirObjectToStringMapper = new FhirObjectPrinter(); - private final String name; - + private final String myName; + private final AtomicLong myLastInvoke = new AtomicLong(); private final AtomicReference myCountdownLatch = new AtomicReference<>(); + private final AtomicReference myCountdownLatchSetStacktrace = new AtomicReference<>(); private final AtomicReference> myFailures = new AtomicReference<>(); private final AtomicReference> myCalledWith = new AtomicReference<>(); - private int myDefaultTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS; private final Pointcut myPointcut; + private int myDefaultTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS; private int myInitialCount; - - + private boolean myExactMatch; public PointcutLatch(Pointcut thePointcut) { - this.name = thePointcut.name(); + this.myName = thePointcut.name(); myPointcut = thePointcut; } + public PointcutLatch(String theName) { - this.name = theName; + this.myName = theName; myPointcut = null; } + public long getLastInvoke() { + return myLastInvoke.get(); + } + public PointcutLatch setDefaultTimeoutSeconds(int theDefaultTimeoutSeconds) { myDefaultTimeoutSeconds = theDefaultTimeoutSeconds; return this; } @Override - public void setExpectedCount(int count) { - if (myCountdownLatch.get() != null) { - throw new PointcutLatchException("setExpectedCount() called before previous awaitExpected() completed."); - } - createLatch(count); - ourLog.info("Expecting {} calls to {} latch", count, name); + public void setExpectedCount(int theCount) { + this.setExpectedCount(theCount, true); } - private void createLatch(int count) { + public void setExpectedCount(int theCount, boolean theExactMatch) { + if (myCountdownLatch.get() != null) { + String previousStack = myCountdownLatchSetStacktrace.get(); + throw new PointcutLatchException("setExpectedCount() called before previous awaitExpected() completed. Previous set stack:\n" + previousStack); + } + myExactMatch = theExactMatch; + createLatch(theCount); + if (theExactMatch) { + ourLog.info("Expecting exactly {} calls to {} latch", theCount, myName); + } else { + ourLog.info("Expecting at least {} calls to {} latch", theCount, myName); + } + } + + public void setExpectAtLeast(int theCount) { + setExpectedCount(theCount, false); + } + + public boolean isSet() { + return myCountdownLatch.get() != null; + } + + private void createLatch(int theCount) { myFailures.set(Collections.synchronizedList(new ArrayList<>())); myCalledWith.set(Collections.synchronizedList(new ArrayList<>())); - myCountdownLatch.set(new CountDownLatch(count)); - myInitialCount = count; + myCountdownLatch.set(new CountDownLatch(theCount)); + try { + throw new Exception(); + } catch (Exception e) { + myCountdownLatchSetStacktrace.set(ExceptionUtils.getStackTrace(e)); + } + myInitialCount = theCount; } private void addFailure(String failure) { @@ -93,7 +123,7 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { } private String getName() { - return name + " " + this.getClass().getSimpleName(); + return myName + " " + this.getClass().getSimpleName(); } @Override @@ -133,6 +163,7 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { @Override public void clear() { myCountdownLatch.set(null); + myCountdownLatchSetStacktrace.set(null); } private String toCalledWithString() { @@ -152,17 +183,23 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { @Override public void invoke(Pointcut thePointcut, HookParams theArgs) { + myLastInvoke.set(System.currentTimeMillis()); + CountDownLatch latch = myCountdownLatch.get(); - if (latch == null) { - throw new PointcutLatchException("invoke() called outside of setExpectedCount() .. awaitExpected(). Probably got more invocations than expected or clear() was called before invoke() arrived.", theArgs); - } else if (latch.getCount() <= 0) { - addFailure("invoke() called when countdown was zero."); + if (myExactMatch) { + if (latch == null) { + throw new PointcutLatchException("invoke() for " + myName + " called outside of setExpectedCount() .. awaitExpected(). Probably got more invocations than expected or clear() was called before invoke() arrived with args: " + theArgs, theArgs); + } else if (latch.getCount() <= 0) { + addFailure("invoke() called when countdown was zero."); + } + } else if (latch == null || latch.getCount() <= 0) { + return; } if (myCalledWith.get() != null) { myCalledWith.get().add(theArgs); } - ourLog.info("Called {} {} with {}", name, latch, hookParamsToString(theArgs)); + ourLog.info("Called {} {} with {}", myName, latch, hookParamsToString(theArgs)); latch.countDown(); } @@ -171,6 +208,21 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { this.invoke(myPointcut, new HookParams(arg)); } + @Override + public String toString() { + return new ToStringBuilder(this) + .append("name", myName) + .append("myCountdownLatch", myCountdownLatch) +// .append("myFailures", myFailures) +// .append("myCalledWith", myCalledWith) + .append("myInitialCount", myInitialCount) + .toString(); + } + + public Object getLatchInvocationParameter() { + return getLatchInvocationParameter(myCalledWith.get()); + } + private class PointcutLatchException extends IllegalStateException { private static final long serialVersionUID = 1372636272233536829L; @@ -187,21 +239,6 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { return hookParams.values().stream().map(ourFhirObjectToStringMapper).collect(Collectors.joining(", ")); } - @Override - public String toString() { - return new ToStringBuilder(this) - .append("name", name) - .append("myCountdownLatch", myCountdownLatch) -// .append("myFailures", myFailures) -// .append("myCalledWith", myCalledWith) - .append("myInitialCount", myInitialCount) - .toString(); - } - - public Object getLatchInvocationParameter() { - return getLatchInvocationParameter(myCalledWith.get()); - } - public static Object getLatchInvocationParameter(List theHookParams) { Validate.notNull(theHookParams); Validate.isTrue(theHookParams.size() == 1, "Expected Pointcut to be invoked 1 time"); diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 107e75d8313..58ca47b8214 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml @@ -66,6 +66,10 @@ ${project.version} + + com.google.code.gson + gson + org.thymeleaf thymeleaf diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index 9ed40191388..c0af8e3ea98 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -1,42 +1,59 @@ package ca.uhn.fhir.to; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.client.impl.GenericClient; +import ca.uhn.fhir.rest.gclient.ICreateTyped; +import ca.uhn.fhir.rest.gclient.IHistory; +import ca.uhn.fhir.rest.gclient.IHistoryTyped; +import ca.uhn.fhir.rest.gclient.IHistoryUntyped; +import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.IUntypedQuery; +import ca.uhn.fhir.rest.gclient.NumberClientParam.IMatches; +import ca.uhn.fhir.rest.gclient.QuantityClientParam; +import ca.uhn.fhir.rest.gclient.QuantityClientParam.IAndUnits; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.to.model.HomeRequest; +import ca.uhn.fhir.to.model.ResourceRequest; +import ca.uhn.fhir.to.model.TransactionRequest; +import com.google.gson.stream.JsonWriter; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.dstu3.model.CapabilityStatement; +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.StringType; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseConformance; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; + import static org.apache.commons.lang3.StringUtils.defaultIfEmpty; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.io.IOException; -import java.io.StringWriter; -import java.util.*; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu3.model.CapabilityStatement; -import org.hl7.fhir.dstu3.model.CapabilityStatement.*; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.instance.model.api.*; -import org.springframework.ui.ModelMap; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.RequestMapping; - -import com.google.gson.stream.JsonWriter; - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.dstu2.resource.Conformance; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.client.impl.GenericClient; -import ca.uhn.fhir.rest.gclient.*; -import ca.uhn.fhir.rest.gclient.NumberClientParam.IMatches; -import ca.uhn.fhir.rest.gclient.QuantityClientParam.IAndUnits; -import ca.uhn.fhir.to.model.*; - @org.springframework.stereotype.Controller() public class Controller extends BaseController { static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Controller.class); diff --git a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java index e0a78fd7b41..f2a193932e9 100644 --- a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java +++ b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java @@ -1,26 +1,13 @@ package ca.uhn.fhir.jpa.test; -import java.util.Properties; - //import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; -import org.apache.commons.dbcp2.BasicDataSource; -import org.apache.commons.lang3.time.DateUtils; //import org.hibernate.jpa.HibernatePersistenceProvider; -import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.annotation.EnableTransactionManagement; + import org.springframework.transaction.annotation.EnableTransactionManagement; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; + import ca.uhn.fhir.jpa.api.config.DaoConfig; @Configuration @EnableTransactionManagement() diff --git a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java index 38e511c6caf..d039fe10247 100644 --- a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java +++ b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java @@ -5,10 +5,7 @@ import java.util.List; import java.util.Set; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever; -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.RestfulServer; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml index 65e49701718..e28d7475d5e 100644 --- a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml +++ b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -13,7 +13,7 @@ - + @@ -47,4 +47,4 @@ - \ No newline at end of file + diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index c4bae7d6848..2f55922f38f 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index d8b4b070e80..e537f31e90a 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/profiles.properties b/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/profiles.properties new file mode 100644 index 00000000000..a299c930848 --- /dev/null +++ b/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/profiles.properties @@ -0,0 +1,161 @@ +account.profile.xml=true +address.profile.xml=true +age.profile.xml=true +allergyintolerance.profile.xml=true +annotation.profile.xml=true +appointment.profile.xml=true +appointmentresponse.profile.xml=true +attachment.profile.xml=true +auditevent.profile.xml=true +backboneelement.profile.xml=true +base64binary.profile.xml=true +basic.profile.xml=true +binary.profile.xml=true +bodysite.profile.xml=true +boolean.profile.xml=true +bundle.profile.xml=true +careplan.profile.xml=true +cholesterol.profile.xml=true +claim.profile.xml=true +claimresponse.profile.xml=true +clinicaldocument.profile.xml=true +clinicalimpression.profile.xml=true +code.profile.xml=true +codeableconcept.profile.xml=true +coding.profile.xml=true +communication.profile.xml=true +communicationrequest.profile.xml=true +composition.profile.xml=true +conceptmap.profile.xml=true +condition.profile.xml=true +conformance.profile.xml=true +consentdirective.profile.xml=true +contactpoint.profile.xml=true +contract.profile.xml=true +contraindication.profile.xml=true +count.profile.xml=true +coverage.profile.xml=true +dataelement.profile.xml=true +date.profile.xml=true +datetime.profile.xml=true +decimal.profile.xml=true +definition.profile.xml=true +detectedissue.profile.xml=true +device.profile.xml=true +devicecomponent.profile.xml=true +devicemetric.profile.xml=true +devicemetricobservation.profile.xml=true +deviceuserequest.profile.xml=true +deviceusestatement.profile.xml=true +diagnosticorder.profile.xml=true +diagnosticreport.profile.xml=true +distance.profile.xml=true +documentmanifest.profile.xml=true +documentreference.profile.xml=true +domainresource.profile.xml=true +duration.profile.xml=true +element.profile.xml=true +elementdefinition-de.profile.xml=true +elementdefinition.profile.xml=true +eligibilityrequest.profile.xml=true +eligibilityresponse.profile.xml=true +encounter.profile.xml=true +enrollmentrequest.profile.xml=true +enrollmentresponse.profile.xml=true +episodeofcare.profile.xml=true +explanationofbenefit.profile.xml=true +extension.profile.xml=true +familymemberhistory-genetic.profile.xml=true +familymemberhistory.profile.xml=true +flag.profile.xml=true +genetics.profile.xml=true +geneticsmockup.profile.xml=true +goal.profile.xml=true +group.profile.xml=true +hdlcholesterol.profile.xml=true +healthcareservice.profile.xml=true +humanname.profile.xml=true +id.profile.xml=true +identifier.profile.xml=true +imagingobjectselection.profile.xml=true +imagingstudy.profile.xml=true +immunization.profile.xml=true +immunizationrecommendation.profile.xml=true +implementationguide.profile.xml=true +instant.profile.xml=true +integer.profile.xml=true +ldlcholesterol.profile.xml=true +lipidprofile.profile.xml=true +list.profile.xml=true +location.profile.xml=true +markdown.profile.xml=true +measurereport.profile.xml=true +media.profile.xml=true +medication.profile.xml=true +medicationadministration.profile.xml=true +medicationdispense.profile.xml=true +medicationorder.profile.xml=true +medicationstatement.profile.xml=true +messageheader.profile.xml=true +meta.profile.xml=true +money.profile.xml=true +namingsystem.profile.xml=true +narrative.profile.xml=true +nutritionorder.profile.xml=true +observation.profile.xml=true +oid.profile.xml=true +operationdefinition.profile.xml=true +operationoutcome.profile.xml=true +order.profile.xml=true +orderresponse.profile.xml=true +organization.profile.xml=true +parameters.profile.xml=true +patient.profile.xml=true +paymentnotice.profile.xml=true +paymentreconciliation.profile.xml=true +period.profile.xml=true +person.profile.xml=true +positiveint.profile.xml=true +practitioner.profile.xml=true +procedure.profile.xml=true +procedurerequest.profile.xml=true +processrequest.profile.xml=true +processresponse.profile.xml=true +provenance.profile.xml=true +quantity.profile.xml=true +questionnaire.profile.xml=true +questionnaireanswers.profile.xml=true +questionnaireresponse.profile.xml=true +range.profile.xml=true +ratio.profile.xml=true +reference.profile.xml=true +referralrequest.profile.xml=true +relatedperson.profile.xml=true +resource.profile.xml=true +riskassessment.profile.xml=true +sampleddata.profile.xml=true +schedule.profile.xml=true +searchparameter.profile.xml=true +shareablevalueset.profile.xml=true +signature.profile.xml=true +simplequantity.profile.xml=true +slot.profile.xml=true +specimen.profile.xml=true +string.profile.xml=true +structuredefinition.profile.xml=true +subscription.profile.xml=true +substance.profile.xml=true +supplydelivery.profile.xml=true +supplyrequest.profile.xml=true +testscript.profile.xml=true +time.profile.xml=true +timing.profile.xml=true +triglyceride.profile.xml=true +unsignedint.profile.xml=true +uri.profile.xml=true +uuid.profile.xml=true +valueset.profile.xml=true +visionprescription.profile.xml=true +xdsdocumentmanifest.profile.xml=true +xdsdocumentreference.profile.xml=true +xhtml.profile.xml=true diff --git a/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/xhtml.profile.xml b/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/xhtml.profile.xml new file mode 100644 index 00000000000..605ba9eaf05 --- /dev/null +++ b/hapi-fhir-validation-resources-dstu2/src/main/resources/org/hl7/fhir/instance/model/profile/xhtml.profile.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 152a124fc68..a3c63ac2459 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 901d583f414..a55d4a5fbf0 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 708cbe421a7..e5510285b58 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 9a7e1c13971..8fe9898edcb 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -31,6 +31,11 @@ org.hl7.fhir.validation ${fhir_core_version} + + ca.uhn.hapi.fhir + org.hl7.fhir.utilities + ${fhir_core_version} + com.github.ben-manes.caffeine @@ -41,6 +46,15 @@ jsr305 + + + com.google.code.gson + gson + + diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseStaticResourceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseStaticResourceValidationSupport.java new file mode 100644 index 00000000000..6ef2b5966d0 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseStaticResourceValidationSupport.java @@ -0,0 +1,27 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public abstract class BaseStaticResourceValidationSupport extends BaseValidationSupport implements IValidationSupport { + + /** + * Constructor + */ + protected BaseStaticResourceValidationSupport(FhirContext theFhirContext) { + super(theFhirContext); + } + + @SuppressWarnings("unchecked") + static List toList(Map theMap) { + ArrayList retVal = new ArrayList<>(theMap.values()); + return (List) Collections.unmodifiableList(retVal); + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupport.java new file mode 100644 index 00000000000..adc638150fb --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupport.java @@ -0,0 +1,22 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import org.apache.commons.lang3.Validate; + +public abstract class BaseValidationSupport implements IValidationSupport { + protected final FhirContext myCtx; + + /** + * Constructor + */ + public BaseValidationSupport(FhirContext theFhirContext) { + Validate.notNull(theFhirContext, "theFhirContext must not be null"); + myCtx = theFhirContext; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java new file mode 100644 index 00000000000..380aedfef9b --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java @@ -0,0 +1,102 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * This class is a wrapper for an existing {@link @IContextValidationSupport} object, intended to be + * subclassed in order to layer functionality on top of the existing validation support object. + * + * @since 5.0.0 + */ +public class BaseValidationSupportWrapper extends BaseValidationSupport { + private final IValidationSupport myWrap; + + /** + * Constructor + * + * @param theFhirContext The FhirContext object (must be initialized for the appropriate FHIR version) + * @param theWrap The validation support object to wrap + */ + public BaseValidationSupportWrapper(FhirContext theFhirContext, IValidationSupport theWrap) { + super(theFhirContext); + Validate.notNull(theWrap, "theWrap must not be null"); + + myWrap = theWrap; + } + + @Override + public List fetchAllConformanceResources() { + return myWrap.fetchAllConformanceResources(); + } + + @Override + public List fetchAllStructureDefinitions() { + return myWrap.fetchAllStructureDefinitions(); + } + + @Override + public T fetchResource(Class theClass, String theUri) { + return myWrap.fetchResource(theClass, theUri); + } + + @Override + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + return myWrap.isCodeSystemSupported(myWrap, theSystem); + } + + @Override + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + return myWrap.validateCode(theRootValidationSupport, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl); + } + + @Override + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return myWrap.lookupCode(theRootValidationSupport, theSystem, theCode); + } + + @Override + public boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + return myWrap.isValueSetSupported(myWrap, theValueSetUrl); + } + + @Override + public IValidationSupport.ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { + return myWrap.expandValueSet(theRootValidationSupport, null, theValueSetToExpand); + } + + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + return myWrap.fetchCodeSystem(theSystem); + } + + @Override + public IBaseResource fetchValueSet(String theUri) { + return myWrap.fetchValueSet(theUri); + } + + + @Override + public IBaseResource fetchStructureDefinition(String theUrl) { + return myWrap.fetchStructureDefinition(theUrl); + } + + @Override + public IBaseResource generateSnapshot(IValidationSupport theRootValidationSupport, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) { + return myWrap.generateSnapshot(theRootValidationSupport, theInput, theUrl, theWebUrl, theProfileName); + } + + @Override + public IValidationSupport.CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + return myWrap.validateCodeInValueSet(theRootValidationSupport, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet); + } + + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java new file mode 100644 index 00000000000..423bf0221be --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java @@ -0,0 +1,104 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; + +@SuppressWarnings("unchecked") +public class CachingValidationSupport extends BaseValidationSupportWrapper implements IValidationSupport { + + private static final Logger ourLog = LoggerFactory.getLogger(CachingValidationSupport.class); + private final Cache myCache; + private final Cache myValidateCodeCache; + private final Cache myLookupCodeCache; + + + public CachingValidationSupport(IValidationSupport theWrap) { + super(theWrap.getFhirContext(), theWrap); + myValidateCodeCache = Caffeine + .newBuilder() + .expireAfterWrite(60, TimeUnit.SECONDS) + .maximumSize(5000) + .build(); + myLookupCodeCache = Caffeine + .newBuilder() + .expireAfterWrite(60, TimeUnit.SECONDS) + .maximumSize(5000) + .build(); + myCache = Caffeine + .newBuilder() + .expireAfterWrite(60, TimeUnit.SECONDS) + .maximumSize(5000) + .build(); + } + + @Override + public List fetchAllConformanceResources() { + String key = "fetchAllConformanceResources"; + return loadFromCache(myCache, key, t -> super.fetchAllConformanceResources()); + } + + @Override + public List fetchAllStructureDefinitions() { + String key = "fetchAllStructureDefinitions"; + return loadFromCache(myCache, key, t -> super.fetchAllStructureDefinitions()); + } + + @Override + public T fetchResource(Class theClass, String theUri) { + return loadFromCache(myCache, "fetchResource " + theClass.getName() + " " + theUri, + t -> super.fetchResource(theClass, theUri)); + } + + @Override + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + String key = "isCodeSystemSupported " + theSystem; + Boolean retVal = loadFromCache(myCache, key, t -> super.isCodeSystemSupported(theRootValidationSupport, theSystem)); + assert retVal != null; + return retVal; + } + + @Override + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS"); + return loadFromCache(myValidateCodeCache, key, t -> super.validateCode(theRootValidationSupport, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl)); + } + + @Override + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + String key = "lookupCode " + theSystem + " " + theCode; + return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theRootValidationSupport, theSystem, theCode)); + } + + @SuppressWarnings("OptionalAssignedToNull") + @Nullable + private T loadFromCache(Cache theCache, String theKey, Function theLoader) { + ourLog.trace("Fetching from cache: {}", theKey); + + Function> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey)); + Optional result = (Optional) theCache.get(theKey, loaderWrapper); + assert result != null; + + return result.orElse(null); + + } + + @Override + public void invalidateCaches() { + myLookupCodeCache.invalidateAll(); + myCache.invalidateAll(); + myValidateCodeCache.invalidateAll(); + } +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java new file mode 100644 index 00000000000..5de2f64ed70 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -0,0 +1,382 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.dstu2.model.ValueSet; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * This {@link IValidationSupport validation support module} can be used to validate codes against common + * CodeSystems that are commonly used, but are not distriuted with the FHIR specification for various reasons + * (size, complexity, etc.). + *

        + * See CommonCodeSystemsTerminologyService in the HAPI FHIR documentation + * for details about what is and isn't covered by this class. + *

        + */ +public class CommonCodeSystemsTerminologyService implements IValidationSupport { + public static final String LANGUAGES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/languages"; + public static final String MIMETYPES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/mimetypes"; + public static final String CURRENCIES_CODESYSTEM_URL = "urn:iso:std:iso:4217"; + public static final String CURRENCIES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/currencies"; + private static final String USPS_CODESYSTEM_URL = "https://www.usps.com/"; + private static final String USPS_VALUESET_URL = "http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state"; + private static Map USPS_CODES = Collections.unmodifiableMap(buildUspsCodes()); + private static Map ISO_4217_CODES = Collections.unmodifiableMap(buildIso4217Codes()); + + + private final FhirContext myFhirContext; + + /** + * Constructor + */ + public CommonCodeSystemsTerminologyService(FhirContext theFhirContext) { + Validate.notNull(theFhirContext); + + myFhirContext = theFhirContext; + } + + @Override + public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + String url = getValueSetUrl(theValueSet); + + /* ************************************************************************************** + * NOTE: Update validation_support_modules.html if any of the support in this module + * changes in any way! + * **************************************************************************************/ + + Map handlerMap = null; + String expectSystem = null; + switch (url) { + case USPS_VALUESET_URL: + handlerMap = USPS_CODES; + expectSystem = USPS_CODESYSTEM_URL; + break; + + case CURRENCIES_VALUESET_URL: + handlerMap = ISO_4217_CODES; + expectSystem = CURRENCIES_CODESYSTEM_URL; + break; + + case LANGUAGES_VALUESET_URL: + case MIMETYPES_VALUESET_URL: + // This is a pretty naive implementation - Should be enhanced in future + return new CodeValidationResult() + .setCode(theCode) + .setDisplay(theDisplay); + } + + + if (handlerMap != null) { + String display = handlerMap.get(theCode); + if (display != null) { + if (expectSystem.equals(theCodeSystem) || theOptions.isInferSystem()) { + return new CodeValidationResult() + .setCode(theCode) + .setDisplay(display); + } + } + + return new CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setMessage("Code \"" + theCode + "\" is not in system: " + USPS_CODESYSTEM_URL); + } + + return null; + } + + public String getValueSetUrl(@Nonnull IBaseResource theValueSet) { + String url; + switch (getFhirContext().getVersion().getVersion()) { + case DSTU2: { + url = ((ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSet).getUrl(); + break; + } + case DSTU2_HL7ORG: { + url = ((ValueSet) theValueSet).getUrl(); + break; + } + case DSTU3: { + url = ((org.hl7.fhir.dstu3.model.ValueSet) theValueSet).getUrl(); + break; + } + case R4: { + url = ((org.hl7.fhir.r4.model.ValueSet) theValueSet).getUrl(); + break; + } + case R5: { + url = ((org.hl7.fhir.r5.model.ValueSet) theValueSet).getUrl(); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + getFhirContext().getVersion().getVersion()); + } + return url; + } + + @Override + public FhirContext getFhirContext() { + return myFhirContext; + } + + private static HashMap buildUspsCodes() { + HashMap uspsCodes = new HashMap<>(); + uspsCodes.put("AK", "Alaska"); + uspsCodes.put("AL", "Alabama"); + uspsCodes.put("AR", "Arkansas"); + uspsCodes.put("AS", "American Samoa"); + uspsCodes.put("AZ", "Arizona"); + uspsCodes.put("CA", "California"); + uspsCodes.put("CO", "Colorado"); + uspsCodes.put("CT", "Connecticut"); + uspsCodes.put("DC", "District of Columbia"); + uspsCodes.put("DE", "Delaware"); + uspsCodes.put("FL", "Florida"); + uspsCodes.put("FM", "Federated States of Micronesia"); + uspsCodes.put("GA", "Georgia"); + uspsCodes.put("GU", "Guam"); + uspsCodes.put("HI", "Hawaii"); + uspsCodes.put("IA", "Iowa"); + uspsCodes.put("ID", "Idaho"); + uspsCodes.put("IL", "Illinois"); + uspsCodes.put("IN", "Indiana"); + uspsCodes.put("KS", "Kansas"); + uspsCodes.put("KY", "Kentucky"); + uspsCodes.put("LA", "Louisiana"); + uspsCodes.put("MA", "Massachusetts"); + uspsCodes.put("MD", "Maryland"); + uspsCodes.put("ME", "Maine"); + uspsCodes.put("MH", "Marshall Islands"); + uspsCodes.put("MI", "Michigan"); + uspsCodes.put("MN", "Minnesota"); + uspsCodes.put("MO", "Missouri"); + uspsCodes.put("MP", "Northern Mariana Islands"); + uspsCodes.put("MS", "Mississippi"); + uspsCodes.put("MT", "Montana"); + uspsCodes.put("NC", "North Carolina"); + uspsCodes.put("ND", "North Dakota"); + uspsCodes.put("NE", "Nebraska"); + uspsCodes.put("NH", "New Hampshire"); + uspsCodes.put("NJ", "New Jersey"); + uspsCodes.put("NM", "New Mexico"); + uspsCodes.put("NV", "Nevada"); + uspsCodes.put("NY", "New York"); + uspsCodes.put("OH", "Ohio"); + uspsCodes.put("OK", "Oklahoma"); + uspsCodes.put("OR", "Oregon"); + uspsCodes.put("PA", "Pennsylvania"); + uspsCodes.put("PR", "Puerto Rico"); + uspsCodes.put("PW", "Palau"); + uspsCodes.put("RI", "Rhode Island"); + uspsCodes.put("SC", "South Carolina"); + uspsCodes.put("SD", "South Dakota"); + uspsCodes.put("TN", "Tennessee"); + uspsCodes.put("TX", "Texas"); + uspsCodes.put("UM", "U.S. Minor Outlying Islands"); + uspsCodes.put("UT", "Utah"); + uspsCodes.put("VA", "Virginia"); + uspsCodes.put("VI", "Virgin Islands of the U.S."); + uspsCodes.put("VT", "Vermont"); + uspsCodes.put("WA", "Washington"); + uspsCodes.put("WI", "Wisconsin"); + uspsCodes.put("WV", "West Virginia"); + uspsCodes.put("WY", "Wyoming"); + return uspsCodes; + } + + private static HashMap buildIso4217Codes() { + HashMap iso4217Codes = new HashMap<>(); + iso4217Codes.put("AED", "United Arab Emirates dirham"); + iso4217Codes.put("AFN", "Afghan afghani"); + iso4217Codes.put("ALL", "Albanian lek"); + iso4217Codes.put("AMD", "Armenian dram"); + iso4217Codes.put("ANG", "Netherlands Antillean guilder"); + iso4217Codes.put("AOA", "Angolan kwanza"); + iso4217Codes.put("ARS", "Argentine peso"); + iso4217Codes.put("AUD", "Australian dollar"); + iso4217Codes.put("AWG", "Aruban florin"); + iso4217Codes.put("AZN", "Azerbaijani manat"); + iso4217Codes.put("BAM", "Bosnia and Herzegovina convertible mark"); + iso4217Codes.put("BBD", "Barbados dollar"); + iso4217Codes.put("BDT", "Bangladeshi taka"); + iso4217Codes.put("BGN", "Bulgarian lev"); + iso4217Codes.put("BHD", "Bahraini dinar"); + iso4217Codes.put("BIF", "Burundian franc"); + iso4217Codes.put("BMD", "Bermudian dollar"); + iso4217Codes.put("BND", "Brunei dollar"); + iso4217Codes.put("BOB", "Boliviano"); + iso4217Codes.put("BOV", "Bolivian Mvdol (funds code)"); + iso4217Codes.put("BRL", "Brazilian real"); + iso4217Codes.put("BSD", "Bahamian dollar"); + iso4217Codes.put("BTN", "Bhutanese ngultrum"); + iso4217Codes.put("BWP", "Botswana pula"); + iso4217Codes.put("BYN", "Belarusian ruble"); + iso4217Codes.put("BZD", "Belize dollar"); + iso4217Codes.put("CAD", "Canadian dollar"); + iso4217Codes.put("CDF", "Congolese franc"); + iso4217Codes.put("CHE", "WIR Euro (complementary currency)"); + iso4217Codes.put("CHF", "Swiss franc"); + iso4217Codes.put("CHW", "WIR Franc (complementary currency)"); + iso4217Codes.put("CLF", "Unidad de Fomento (funds code)"); + iso4217Codes.put("CLP", "Chilean peso"); + iso4217Codes.put("CNY", "Renminbi (Chinese) yuan[8]"); + iso4217Codes.put("COP", "Colombian peso"); + iso4217Codes.put("COU", "Unidad de Valor Real (UVR) (funds code)[9]"); + iso4217Codes.put("CRC", "Costa Rican colon"); + iso4217Codes.put("CUC", "Cuban convertible peso"); + iso4217Codes.put("CUP", "Cuban peso"); + iso4217Codes.put("CVE", "Cape Verde escudo"); + iso4217Codes.put("CZK", "Czech koruna"); + iso4217Codes.put("DJF", "Djiboutian franc"); + iso4217Codes.put("DKK", "Danish krone"); + iso4217Codes.put("DOP", "Dominican peso"); + iso4217Codes.put("DZD", "Algerian dinar"); + iso4217Codes.put("EGP", "Egyptian pound"); + iso4217Codes.put("ERN", "Eritrean nakfa"); + iso4217Codes.put("ETB", "Ethiopian birr"); + iso4217Codes.put("EUR", "Euro"); + iso4217Codes.put("FJD", "Fiji dollar"); + iso4217Codes.put("FKP", "Falkland Islands pound"); + iso4217Codes.put("GBP", "Pound sterling"); + iso4217Codes.put("GEL", "Georgian lari"); + iso4217Codes.put("GGP", "Guernsey Pound"); + iso4217Codes.put("GHS", "Ghanaian cedi"); + iso4217Codes.put("GIP", "Gibraltar pound"); + iso4217Codes.put("GMD", "Gambian dalasi"); + iso4217Codes.put("GNF", "Guinean franc"); + iso4217Codes.put("GTQ", "Guatemalan quetzal"); + iso4217Codes.put("GYD", "Guyanese dollar"); + iso4217Codes.put("HKD", "Hong Kong dollar"); + iso4217Codes.put("HNL", "Honduran lempira"); + iso4217Codes.put("HRK", "Croatian kuna"); + iso4217Codes.put("HTG", "Haitian gourde"); + iso4217Codes.put("HUF", "Hungarian forint"); + iso4217Codes.put("IDR", "Indonesian rupiah"); + iso4217Codes.put("ILS", "Israeli new shekel"); + iso4217Codes.put("IMP", "Isle of Man Pound"); + iso4217Codes.put("INR", "Indian rupee"); + iso4217Codes.put("IQD", "Iraqi dinar"); + iso4217Codes.put("IRR", "Iranian rial"); + iso4217Codes.put("ISK", "Icelandic króna"); + iso4217Codes.put("JEP", "Jersey Pound"); + iso4217Codes.put("JMD", "Jamaican dollar"); + iso4217Codes.put("JOD", "Jordanian dinar"); + iso4217Codes.put("JPY", "Japanese yen"); + iso4217Codes.put("KES", "Kenyan shilling"); + iso4217Codes.put("KGS", "Kyrgyzstani som"); + iso4217Codes.put("KHR", "Cambodian riel"); + iso4217Codes.put("KMF", "Comoro franc"); + iso4217Codes.put("KPW", "North Korean won"); + iso4217Codes.put("KRW", "South Korean won"); + iso4217Codes.put("KWD", "Kuwaiti dinar"); + iso4217Codes.put("KYD", "Cayman Islands dollar"); + iso4217Codes.put("KZT", "Kazakhstani tenge"); + iso4217Codes.put("LAK", "Lao kip"); + iso4217Codes.put("LBP", "Lebanese pound"); + iso4217Codes.put("LKR", "Sri Lankan rupee"); + iso4217Codes.put("LRD", "Liberian dollar"); + iso4217Codes.put("LSL", "Lesotho loti"); + iso4217Codes.put("LYD", "Libyan dinar"); + iso4217Codes.put("MAD", "Moroccan dirham"); + iso4217Codes.put("MDL", "Moldovan leu"); + iso4217Codes.put("MGA", "Malagasy ariary"); + iso4217Codes.put("MKD", "Macedonian denar"); + iso4217Codes.put("MMK", "Myanmar kyat"); + iso4217Codes.put("MNT", "Mongolian tögrög"); + iso4217Codes.put("MOP", "Macanese pataca"); + iso4217Codes.put("MRU", "Mauritanian ouguiya"); + iso4217Codes.put("MUR", "Mauritian rupee"); + iso4217Codes.put("MVR", "Maldivian rufiyaa"); + iso4217Codes.put("MWK", "Malawian kwacha"); + iso4217Codes.put("MXN", "Mexican peso"); + iso4217Codes.put("MXV", "Mexican Unidad de Inversion (UDI) (funds code)"); + iso4217Codes.put("MYR", "Malaysian ringgit"); + iso4217Codes.put("MZN", "Mozambican metical"); + iso4217Codes.put("NAD", "Namibian dollar"); + iso4217Codes.put("NGN", "Nigerian naira"); + iso4217Codes.put("NIO", "Nicaraguan córdoba"); + iso4217Codes.put("NOK", "Norwegian krone"); + iso4217Codes.put("NPR", "Nepalese rupee"); + iso4217Codes.put("NZD", "New Zealand dollar"); + iso4217Codes.put("OMR", "Omani rial"); + iso4217Codes.put("PAB", "Panamanian balboa"); + iso4217Codes.put("PEN", "Peruvian Sol"); + iso4217Codes.put("PGK", "Papua New Guinean kina"); + iso4217Codes.put("PHP", "Philippine piso[13]"); + iso4217Codes.put("PKR", "Pakistani rupee"); + iso4217Codes.put("PLN", "Polish złoty"); + iso4217Codes.put("PYG", "Paraguayan guaraní"); + iso4217Codes.put("QAR", "Qatari riyal"); + iso4217Codes.put("RON", "Romanian leu"); + iso4217Codes.put("RSD", "Serbian dinar"); + iso4217Codes.put("RUB", "Russian ruble"); + iso4217Codes.put("RWF", "Rwandan franc"); + iso4217Codes.put("SAR", "Saudi riyal"); + iso4217Codes.put("SBD", "Solomon Islands dollar"); + iso4217Codes.put("SCR", "Seychelles rupee"); + iso4217Codes.put("SDG", "Sudanese pound"); + iso4217Codes.put("SEK", "Swedish krona/kronor"); + iso4217Codes.put("SGD", "Singapore dollar"); + iso4217Codes.put("SHP", "Saint Helena pound"); + iso4217Codes.put("SLL", "Sierra Leonean leone"); + iso4217Codes.put("SOS", "Somali shilling"); + iso4217Codes.put("SRD", "Surinamese dollar"); + iso4217Codes.put("SSP", "South Sudanese pound"); + iso4217Codes.put("STN", "São Tomé and Príncipe dobra"); + iso4217Codes.put("SVC", "Salvadoran colón"); + iso4217Codes.put("SYP", "Syrian pound"); + iso4217Codes.put("SZL", "Swazi lilangeni"); + iso4217Codes.put("THB", "Thai baht"); + iso4217Codes.put("TJS", "Tajikistani somoni"); + iso4217Codes.put("TMT", "Turkmenistan manat"); + iso4217Codes.put("TND", "Tunisian dinar"); + iso4217Codes.put("TOP", "Tongan paʻanga"); + iso4217Codes.put("TRY", "Turkish lira"); + iso4217Codes.put("TTD", "Trinidad and Tobago dollar"); + iso4217Codes.put("TVD", "Tuvalu Dollar"); + iso4217Codes.put("TWD", "New Taiwan dollar"); + iso4217Codes.put("TZS", "Tanzanian shilling"); + iso4217Codes.put("UAH", "Ukrainian hryvnia"); + iso4217Codes.put("UGX", "Ugandan shilling"); + iso4217Codes.put("USD", "United States dollar"); + iso4217Codes.put("USN", "United States dollar (next day) (funds code)"); + iso4217Codes.put("UYI", "Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code)"); + iso4217Codes.put("UYU", "Uruguayan peso"); + iso4217Codes.put("UZS", "Uzbekistan som"); + iso4217Codes.put("VEF", "Venezuelan bolívar"); + iso4217Codes.put("VND", "Vietnamese đồng"); + iso4217Codes.put("VUV", "Vanuatu vatu"); + iso4217Codes.put("WST", "Samoan tala"); + iso4217Codes.put("XAF", "CFA franc BEAC"); + iso4217Codes.put("XAG", "Silver (one troy ounce)"); + iso4217Codes.put("XAU", "Gold (one troy ounce)"); + iso4217Codes.put("XBA", "European Composite Unit (EURCO) (bond market unit)"); + iso4217Codes.put("XBB", "European Monetary Unit (E.M.U.-6) (bond market unit)"); + iso4217Codes.put("XBC", "European Unit of Account 9 (E.U.A.-9) (bond market unit)"); + iso4217Codes.put("XBD", "European Unit of Account 17 (E.U.A.-17) (bond market unit)"); + iso4217Codes.put("XCD", "East Caribbean dollar"); + iso4217Codes.put("XDR", "Special drawing rights"); + iso4217Codes.put("XOF", "CFA franc BCEAO"); + iso4217Codes.put("XPD", "Palladium (one troy ounce)"); + iso4217Codes.put("XPF", "CFP franc (franc Pacifique)"); + iso4217Codes.put("XPT", "Platinum (one troy ounce)"); + iso4217Codes.put("XSU", "SUCRE"); + iso4217Codes.put("XTS", "Code reserved for testing purposes"); + iso4217Codes.put("XUA", "ADB Unit of Account"); + iso4217Codes.put("XXX", "No currency"); + iso4217Codes.put("YER", "Yemeni rial"); + iso4217Codes.put("ZAR", "South African rand"); + iso4217Codes.put("ZMW", "Zambian kwacha"); + iso4217Codes.put("ZWL", "Zimbabwean dollar A/10"); + return iso4217Codes; + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java new file mode 100644 index 00000000000..0b608ede8b1 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -0,0 +1,496 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.util.VersionIndependentConcept; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.convertors.conv10_50.ValueSet10_50; +import org.hl7.fhir.convertors.conv30_50.CodeSystem30_50; +import org.hl7.fhir.convertors.conv30_50.ValueSet30_50; +import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; +import org.hl7.fhir.convertors.conv40_50.ValueSet40_50; +import org.hl7.fhir.dstu2.model.ValueSet; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes + * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology + * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an + * external term service API) + */ +public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { + private final FhirContext myCtx; + + public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { + Validate.notNull(theCtx, "theCtx must not be null"); + myCtx = theCtx; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } + + @Override + public ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { + + org.hl7.fhir.r5.model.ValueSet expansionR5 = expandValueSetToCanonical(theRootValidationSupport, theValueSetToExpand); + if (expansionR5 == null) { + return null; + } + + IBaseResource expansion; + switch (myCtx.getVersion().getVersion()) { + case DSTU2_HL7ORG: { + expansion = ValueSet10_50.convertValueSet(expansionR5); + break; + } + case DSTU3: { + expansion = ValueSet30_50.convertValueSet(expansionR5); + break; + } + case R4: { + expansion = ValueSet40_50.convertValueSet(expansionR5); + break; + } + case R5: { + expansion = expansionR5; + break; + } + case DSTU2: + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); + } + + return new ValueSetExpansionOutcome(expansion, null); + } + + private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(IValidationSupport theRootValidationSupport, IBaseResource theValueSetToExpand) { + org.hl7.fhir.r5.model.ValueSet expansionR5; + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: { + expansionR5 = expandValueSetDstu2Hl7Org(theRootValidationSupport, (ValueSet) theValueSetToExpand); + break; + } + case DSTU3: { + expansionR5 = expandValueSetDstu3(theRootValidationSupport, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand); + break; + } + case R4: { + expansionR5 = expandValueSetR4(theRootValidationSupport, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand); + break; + } + case R5: { + expansionR5 = expandValueSetR5(theRootValidationSupport, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); + } + + if (expansionR5 == null) { + return null; + } + return expansionR5; + } + + @Override + public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theRootValidationSupport, theValueSet); + if (expansion == null) { + return null; + } + return validateCodeInExpandedValueSet(theRootValidationSupport, theOptions, theCodeSystem, theCode, expansion); + } + + + @Override + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + IBaseResource vs; + if (isNotBlank(theValueSetUrl)) { + vs = theRootValidationSupport.fetchValueSet(theValueSetUrl); + if (vs == null) { + return null; + } + } else { + switch (myCtx.getVersion().getVersion()) { + case DSTU2_HL7ORG: + vs = new org.hl7.fhir.dstu2.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + break; + case DSTU3: + vs = new org.hl7.fhir.dstu3.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + break; + case R4: + vs = new org.hl7.fhir.r4.model.ValueSet() + .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + break; + case R5: + vs = new org.hl7.fhir.r5.model.ValueSet() + .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + break; + case DSTU2: + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); + } + } + + ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theRootValidationSupport, null, vs); + if (valueSetExpansionOutcome == null) { + return null; + } + + IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); + + return validateCodeInExpandedValueSet(theRootValidationSupport, theOptions, theCodeSystem, theCode, expansion); + + } + + private CodeValidationResult validateCodeInExpandedValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, IBaseResource theExpansion) { + assert theExpansion != null; + + boolean caseSensitive = true; + IBaseResource system = null; + if (!theOptions.isInferSystem() && isNotBlank(theCodeSystem)) { + system = theRootValidationSupport.fetchCodeSystem(theCodeSystem); + if (system == null) { + return null; + } + } + + List codes = new ArrayList<>(); + switch (theExpansion.getStructureFhirVersionEnum()) { + case DSTU2_HL7ORG: { + ValueSet expansionVs = (ValueSet) theExpansion; + List contains = expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu2(contains, codes); + break; + } + case DSTU3: { + org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; + List contains = expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu3(contains, codes); + break; + } + case R4: { + org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; + List contains = expansionVs.getExpansion().getContains(); + flattenAndConvertCodesR4(contains, codes); + break; + } + case R5: { + org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; + List contains = expansionVs.getExpansion().getContains(); + flattenAndConvertCodesR5(contains, codes); + break; + } + case DSTU2: + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); + } + + String codeSystemName = null; + String codeSystemVersion = null; + if (system != null) { + switch (system.getStructureFhirVersionEnum()) { + case DSTU2_HL7ORG: { + caseSensitive = true; + break; + } + case DSTU3: { + org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) system; + caseSensitive = systemDstu3.getCaseSensitive(); + codeSystemName = systemDstu3.getName(); + codeSystemVersion = systemDstu3.getVersion(); + break; + } + case R4: { + org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) system; + caseSensitive = systemR4.getCaseSensitive(); + codeSystemName = systemR4.getName(); + codeSystemVersion = systemR4.getVersion(); + break; + } + case R5: { + CodeSystem systemR5 = (CodeSystem) system; + caseSensitive = systemR5.getCaseSensitive(); + codeSystemName = systemR5.getName(); + codeSystemVersion = systemR5.getVersion(); + break; + } + case DSTU2: + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); + } + } + + for (VersionIndependentConcept nextExpansionCode : codes) { + + boolean codeMatches; + if (caseSensitive) { + codeMatches = theCode.equals(nextExpansionCode.getCode()); + } else { + codeMatches = theCode.equalsIgnoreCase(nextExpansionCode.getCode()); + } + if (codeMatches) { + if (theOptions.isInferSystem() || nextExpansionCode.getSystem().equals(theCodeSystem)) { + return new CodeValidationResult() + .setCode(theCode) + .setDisplay(nextExpansionCode.getDisplay()) + .setCodeSystemName(codeSystemName) + .setCodeSystemVersion(codeSystemVersion); + } + } + } + + ValidationMessage.IssueSeverity severity = ValidationMessage.IssueSeverity.ERROR; + + String message = "Unknown code '" + (isNotBlank(theCodeSystem) ? theCodeSystem + "#" : "") + theCode + "'"; + return new CodeValidationResult() + .setSeverityCode(severity.toCode()) + .setMessage(message); + } + + @Override + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + return validateCode(theRootValidationSupport, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(IValidationSupport theRootValidationSupport, ValueSet theInput) { + Function codeSystemLoader = t -> { + org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theRootValidationSupport.fetchCodeSystem(t); + CodeSystem retVal = new CodeSystem(); + addCodesDstu2Hl7Org(codeSystem.getCodeSystem().getConcept(), retVal.getConcept()); + return retVal; + }; + Function valueSetLoader = t -> { + org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theRootValidationSupport.fetchValueSet(t); + return ValueSet10_50.convertValueSet(valueSet); + }; + + org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(theInput); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader); + return (output); + } + + + @Override + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + if (isBlank(theSystem)) { + return false; + } + + IBaseResource cs = theRootValidationSupport.fetchCodeSystem(theSystem); + + if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { + return cs != null; + } + + if (cs != null) { + IPrimitiveType content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); + if (!"not-present".equals(content.getValueAsString())) { + return true; + } + } + + return false; + } + + @Override + public boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + return isNotBlank(theValueSetUrl) && theRootValidationSupport.fetchValueSet(theValueSetUrl) != null; + } + + + private void addCodesDstu2Hl7Org(List theSourceList, List theTargetList) { + for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { + CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); + theTargetList.add(targetConcept); + addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); + } + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(IValidationSupport theRootValidationSupport, org.hl7.fhir.dstu3.model.ValueSet theInput) { + Function codeSystemLoader = t -> { + org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t); + return CodeSystem30_50.convertCodeSystem(codeSystem); + }; + Function valueSetLoader = t -> { + org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theRootValidationSupport.fetchValueSet(t); + return ValueSet30_50.convertValueSet(valueSet); + }; + + org.hl7.fhir.r5.model.ValueSet input = ValueSet30_50.convertValueSet(theInput); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader); + return (output); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(IValidationSupport theRootValidationSupport, org.hl7.fhir.r4.model.ValueSet theInput) { + Function codeSystemLoader = t -> { + org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t); + return CodeSystem40_50.convertCodeSystem(codeSystem); + }; + Function valueSetLoader = t -> { + org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theRootValidationSupport.fetchValueSet(t); + return ValueSet40_50.convertValueSet(valueSet); + }; + + org.hl7.fhir.r5.model.ValueSet input = ValueSet40_50.convertValueSet(theInput); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(input, codeSystemLoader, valueSetLoader); + return (output); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(IValidationSupport theRootValidationSupport, org.hl7.fhir.r5.model.ValueSet theInput) { + Function codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theRootValidationSupport.fetchCodeSystem(t); + Function valueSetLoader = t -> (org.hl7.fhir.r5.model.ValueSet) theRootValidationSupport.fetchValueSet(t); + + return expandValueSetR5(theInput, codeSystemLoader, valueSetLoader); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(org.hl7.fhir.r5.model.ValueSet theInput, Function theCodeSystemLoader, Function theValueSetLoader) { + Set concepts = new HashSet<>(); + + try { + expandValueSetR5IncludeOrExclude(concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true); + expandValueSetR5IncludeOrExclude(concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false); + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + return null; + } + + org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); + for (VersionIndependentConcept next : concepts) { + org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains(); + contains.setSystem(next.getSystem()); + contains.setCode(next.getCode()); + contains.setDisplay(next.getDisplay()); + } + + return retVal; + } + + private void expandValueSetR5IncludeOrExclude(Set theConcepts, Function theCodeSystemLoader, Function theValueSetLoader, List theComposeList, boolean theComposeListIsInclude) throws ExpansionCouldNotBeCompletedInternallyException { + for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { + + List nextCodeList = new ArrayList<>(); + String system = nextInclude.getSystem(); + if (isNotBlank(system)) { + CodeSystem codeSystem = theCodeSystemLoader.apply(system); + if (codeSystem == null) { + throw new ExpansionCouldNotBeCompletedInternallyException(); + } + if (codeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { + throw new ExpansionCouldNotBeCompletedInternallyException(); + } + + Set wantCodes; + if (nextInclude.getConcept().isEmpty()) { + wantCodes = null; + } else { + wantCodes = nextInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); + } + + addCodes(system, codeSystem.getConcept(), nextCodeList, wantCodes); + } + + for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) { + org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString()); + if (vs != null) { + org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(vs, theCodeSystemLoader, theValueSetLoader); + if (subExpansion == null) { + throw new ExpansionCouldNotBeCompletedInternallyException(); + } + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { + nextCodeList.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + } + } + } + + if (theComposeListIsInclude) { + theConcepts.addAll(nextCodeList); + } else { + theConcepts.removeAll(nextCodeList); + } + + } + + } + + private void addCodes(String theSystem, List theSource, List theTarget, Set theCodeFilter) { + for (CodeSystem.ConceptDefinitionComponent next : theSource) { + if (isNotBlank(next.getCode())) { + if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { + theTarget.add(new VersionIndependentConcept(theSystem, next.getCode(), next.getDisplay())); + } + } + addCodes(theSystem, next.getConcept(), theTarget, theCodeFilter); + } + } + + private static class ExpansionCouldNotBeCompletedInternallyException extends Exception { + + } + + private static void flattenAndConvertCodesDstu2(List theInput, List theVersionIndependentConcepts) { + for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu2(next.getContains(), theVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesDstu3(List theInput, List theVersionIndependentConcepts) { + for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu3(next.getContains(), theVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR4(List theInput, List theVersionIndependentConcepts) { + for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesR4(next.getContains(), theVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR5(List theInput, List theVersionIndependentConcepts) { + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesR5(next.getContains(), theVersionIndependentConcepts); + } + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java similarity index 51% rename from hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/PrePopulatedValidationSupport.java rename to hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java index 7faec367141..edd11e70fa0 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java @@ -1,20 +1,21 @@ -package org.hl7.fhir.r5.hapi.validation; +package org.hl7.fhir.common.hapi.validation.support; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.support.IValidationSupport; import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.MetadataResource; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -22,19 +23,18 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * This class is an implementation of {@link IValidationSupport} which may be pre-populated * with a collection of validation resources to be used by the validator. */ -public class PrePopulatedValidationSupport implements IValidationSupport { +public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport { - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; + private final FhirContext myFhirContext; + private final Map myCodeSystems; + private final Map myStructureDefinitions; + private final Map myValueSets; /** * Constructor */ - public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap<>(); - myValueSets = new HashMap<>(); - myCodeSystems = new HashMap<>(); + public PrePopulatedValidationSupport(FhirContext theContext) { + this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>()); } @@ -48,7 +48,13 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are * the resource itself. */ - public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { + public PrePopulatedValidationSupport(FhirContext theFhirContext, Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { + super(theFhirContext); + Validate.notNull(theFhirContext, "theFhirContext must not be null"); + Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); + Validate.notNull(theValueSets, "theValueSets must not be null"); + Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); + myFhirContext = theFhirContext; myStructureDefinitions = theStructureDefinitions; myValueSets = theValueSets; myCodeSystems = theCodeSystems; @@ -68,9 +74,23 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * *

        */ - public void addCodeSystem(CodeSystem theCodeSystem) { - Validate.notBlank(theCodeSystem.getUrl(), "theCodeSystem.getUrl() must not return a value"); - addToMap(theCodeSystem, myCodeSystems, theCodeSystem.getUrl()); + public void addCodeSystem(IBaseResource theCodeSystem) { + String url = processResourceAndReturnUrl(theCodeSystem, "CodeSystem"); + addToMap(theCodeSystem, myCodeSystems, url); + } + + private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) { + Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null"); + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theCodeSystem); + String actualResourceName = resourceDef.getName(); + Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); + + Optional urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theCodeSystem); + String url = urlValue.map(t -> (((IPrimitiveType) t).getValueAsString())).orElse(null); + + Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); + Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); + return url; } /** @@ -87,9 +107,9 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * *

        */ - public void addStructureDefinition(StructureDefinition theStructureDefinition) { - Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value"); - addToMap(theStructureDefinition, myStructureDefinitions, theStructureDefinition.getUrl()); + public void addStructureDefinition(IBaseResource theStructureDefinition) { + String url = processResourceAndReturnUrl(theStructureDefinition, "StructureDefinition"); + addToMap(theStructureDefinition, myStructureDefinitions, url); } private void addToMap(T theStructureDefinition, Map map, String theUrl) { @@ -123,17 +143,13 @@ public class PrePopulatedValidationSupport implements IValidationSupport { *

        */ public void addValueSet(ValueSet theValueSet) { - Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value"); - addToMap(theValueSet, myValueSets, theValueSet.getUrl()); + String url = processResourceAndReturnUrl(theValueSet, "ValueSet"); + addToMap(theValueSet, myValueSets, url); } - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } @Override - public List fetchAllConformanceResources(FhirContext theContext) { + public List fetchAllConformanceResources() { ArrayList retVal = new ArrayList<>(); retVal.addAll(myCodeSystems.values()); retVal.addAll(myStructureDefinitions.values()); @@ -142,59 +158,33 @@ public class PrePopulatedValidationSupport implements IValidationSupport { } @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList(myStructureDefinitions.values()); + public List fetchAllStructureDefinitions() { + return toList(myStructureDefinitions); } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myCodeSystems.get(uri); + public IBaseResource fetchCodeSystem(String theSystem) { + return myCodeSystems.get(theSystem); } @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myValueSets.get(uri); - } - - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) myStructureDefinitions.get(theUri); - } - if (theClass.equals(ValueSet.class)) { - return (T) myValueSets.get(theUri); - } - if (theClass.equals(CodeSystem.class)) { - return (T) myCodeSystems.get(theUri); - } - return null; + public IBaseResource fetchValueSet(String theUri) { + return myValueSets.get(theUri); } @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + public IBaseResource fetchStructureDefinition(String theUrl) { return myStructureDefinitions.get(theUrl); } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + return myCodeSystems.containsKey(theSystem); } @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; + public boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + return myValueSets.containsKey(theValueSetUrl); } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java new file mode 100644 index 00000000000..c2a7fcec04c --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java @@ -0,0 +1,140 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.util.ParametersUtil; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * This class is an implementation of {@link IValidationSupport} that fetches validation codes + * from a remote FHIR based terminology server. It will invoke the FHIR + * ValueSet/$validate-code + * operation in order to validate codes. + */ +public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport { + + private String myBaseUrl; + private List myClientInterceptors = new ArrayList<>(); + + /** + * Constructor + * + * @param theFhirContext The FhirContext object to use + */ + public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext) { + super(theFhirContext); + } + + @Override + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null); + } + + private IGenericClient provideClient() { + IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl); + for (Object next : myClientInterceptors) { + retVal.registerInterceptor(next); + } + return retVal; + } + + @Override + public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, null, theValueSet); + } + + protected CodeValidationResult invokeRemoteValidateCode(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) { + if (isBlank(theCode)) { + return null; + } + + IGenericClient client = provideClient(); + + IBaseParameters input = ParametersUtil.newInstance(getFhirContext()); + + if (isNotBlank(theValueSetUrl)) { + ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "url", theValueSetUrl); + } + ParametersUtil.addParameterToParametersString(getFhirContext(), input, "code", theCode); + if (isNotBlank(theCodeSystem)) { + ParametersUtil.addParameterToParametersUri(getFhirContext(), input, "system", theCodeSystem); + } + if (isNotBlank(theDisplay)) { + ParametersUtil.addParameterToParametersString(getFhirContext(), input, "display", theDisplay); + } + if (theValueSet != null) { + ParametersUtil.addParameterToParameters(getFhirContext(), input, "valueSet", theValueSet); + } + + IBaseParameters output = client + .operation() + .onType("ValueSet") + .named("validate-code") + .withParameters(input) + .execute(); + + List resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result"); + if (resultValues.size() < 1 || isBlank(resultValues.get(0))) { + return null; + } + Validate.isTrue(resultValues.size() == 1, "Response contained %d 'result' values", resultValues.size()); + + boolean success = "true".equalsIgnoreCase(resultValues.get(0)); + + CodeValidationResult retVal = new CodeValidationResult(); + if (success) { + + retVal.setCode(theCode); + List displayValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "display"); + if (displayValues.size() > 0) { + retVal.setDisplay(displayValues.get(0)); + } + + } else { + + retVal.setSeverity(IssueSeverity.ERROR); + List messageValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "message"); + if (messageValues.size() > 0) { + retVal.setMessage(messageValues.get(0)); + } + + } + return retVal; + } + + /** + * Sets the FHIR Terminology Server base URL + * + * @param theBaseUrl The base URL, e.g. "https://hapi.fhir.org/baseR4" + */ + public void setBaseUrl(String theBaseUrl) { + Validate.notBlank(theBaseUrl, "theBaseUrl must be provided"); + myBaseUrl = theBaseUrl; + } + + /** + * Adds an interceptor that will be registered to all clients. + *

        + * Note that this method is not thread-safe and should only be called prior to this module + * being used. + *

        + * + * @param theClientInterceptor The interceptor (must not be null) + */ + public void addClientInterceptor(@Nonnull Object theClientInterceptor) { + Validate.notNull(theClientInterceptor, "theClientInterceptor must not be null"); + myClientInterceptors.add(theClientInterceptor); + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java new file mode 100644 index 00000000000..77c45ad93f7 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java @@ -0,0 +1,194 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.common.hapi.validation.validator.ProfileKnowledgeWorkerR5; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +import java.util.ArrayList; + +/** + * Simple validation support module that handles profile snapshot generation. + *

        + * This module currently supports the following FHIR versions: + *

          + *
        • DSTU3
        • + *
        • R4
        • + *
        • R5
        • + *
        + */ +public class SnapshotGeneratingValidationSupport implements IValidationSupport { + private final FhirContext myCtx; + + /** + * Constructor + */ + public SnapshotGeneratingValidationSupport(FhirContext theCtx) { + Validate.notNull(theCtx); + myCtx = theCtx; + } + + + @Override + public IBaseResource generateSnapshot(IValidationSupport theValidationSupport, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) { + + assert theInput.getStructureFhirVersionEnum() == myCtx.getVersion().getVersion(); + switch (theInput.getStructureFhirVersionEnum()) { + case DSTU3: { + org.hl7.fhir.dstu3.model.StructureDefinition input = (org.hl7.fhir.dstu3.model.StructureDefinition) theInput; + org.hl7.fhir.dstu3.context.IWorkerContext context = new org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext(myCtx, theValidationSupport); + org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorkerDstu3(); + ArrayList messages = new ArrayList<>(); + org.hl7.fhir.dstu3.model.StructureDefinition base = (org.hl7.fhir.dstu3.model.StructureDefinition) theValidationSupport.fetchStructureDefinition(input.getBaseDefinition()); + if (base == null) { + throw new PreconditionFailedException("Unknown base definition: " + input.getBaseDefinition()); + } + new org.hl7.fhir.dstu3.conformance.ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, input, theUrl, theProfileName); + break; + } + case R4: { + org.hl7.fhir.r4.model.StructureDefinition input = (org.hl7.fhir.r4.model.StructureDefinition) theInput; + org.hl7.fhir.r4.context.IWorkerContext context = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(myCtx, theValidationSupport); + org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorkerR4(); + ArrayList messages = new ArrayList<>(); + org.hl7.fhir.r4.model.StructureDefinition base = (org.hl7.fhir.r4.model.StructureDefinition) theValidationSupport.fetchStructureDefinition(input.getBaseDefinition()); + if (base == null) { + throw new PreconditionFailedException("Unknown base definition: " + input.getBaseDefinition()); + } + new org.hl7.fhir.r4.conformance.ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, input, theUrl, theWebUrl, theProfileName); + break; + } + case R5: { + org.hl7.fhir.r5.model.StructureDefinition input = (org.hl7.fhir.r5.model.StructureDefinition) theInput; + org.hl7.fhir.r5.context.IWorkerContext context = new org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext(myCtx, theValidationSupport); + org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(myCtx); + ArrayList messages = new ArrayList<>(); + org.hl7.fhir.r5.model.StructureDefinition base = (org.hl7.fhir.r5.model.StructureDefinition) theValidationSupport.fetchStructureDefinition(input.getBaseDefinition()); + if (base == null) { + throw new PreconditionFailedException("Unknown base definition: " + input.getBaseDefinition()); + } + new org.hl7.fhir.r5.conformance.ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, input, theUrl, theWebUrl, theProfileName); + break; + } + + // NOTE: Add to the class javadoc if you add to this + + case DSTU2: + case DSTU2_HL7ORG: + case DSTU2_1: + default: + throw new IllegalStateException("Can not generate snapshot for version: " + theInput.getStructureFhirVersionEnum()); + } + + return theInput; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } + + + private class MyProfileKnowledgeWorkerR4 implements org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider { + @Override + public boolean isDatatype(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); + } + + @Override + public boolean isResource(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return def instanceof RuntimeResourceDefinition; + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return false; + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + return null; + } + + @Override + public BindingResolution resolveBinding(org.hl7.fhir.r4.model.StructureDefinition def, org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException { + return null; + } + + @Override + public BindingResolution resolveBinding(org.hl7.fhir.r4.model.StructureDefinition def, String url, String path) throws FHIRException { + return null; + } + + @Override + public String getLinkForProfile(org.hl7.fhir.r4.model.StructureDefinition profile, String url) { + return null; + } + + @Override + public boolean prependLinks() { + return false; + } + + @Override + public String getLinkForUrl(String corePath, String url) { + throw new UnsupportedOperationException(); + } + + } + + private class MyProfileKnowledgeWorkerDstu3 implements org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider { + @Override + public boolean isDatatype(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); + } + + @Override + public boolean isResource(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return def instanceof RuntimeResourceDefinition; + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return false; + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + return null; + } + + @Override + public BindingResolution resolveBinding(org.hl7.fhir.dstu3.model.StructureDefinition theStructureDefinition, org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent theElementDefinitionBindingComponent, String theS) { + return null; + } + + @Override + public String getLinkForProfile(org.hl7.fhir.dstu3.model.StructureDefinition theStructureDefinition, String theS) { + return null; + } + + @Override + public boolean prependLinks() { + return false; + } + + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java new file mode 100644 index 00000000000..616fc53a14c --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java @@ -0,0 +1,257 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class ValidationSupportChain implements IValidationSupport { + + private List myChain; + + /** + * Constructor + */ + public ValidationSupportChain() { + myChain = new ArrayList<>(); + } + + /** + * Constructor + */ + public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { + this(); + for (IValidationSupport next : theValidationSupportModules) { + if (next != null) { + addValidationSupport(next); + } + } + } + + @Override + public void invalidateCaches() { + for (IValidationSupport next : myChain) { + next.invalidateCaches(); + } + } + + @Override + public boolean isValueSetSupported(IValidationSupport theRootValidationSupport, String theValueSetUrl) { + for (IValidationSupport next : myChain) { + boolean retVal = next.isValueSetSupported(theRootValidationSupport, theValueSetUrl); + if (retVal) { + return true; + } + } + return false; + } + + @Override + public IBaseResource generateSnapshot(IValidationSupport theRootValidationSupport, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) { + for (IValidationSupport next : myChain) { + IBaseResource retVal = next.generateSnapshot(theRootValidationSupport, theInput, theUrl, theWebUrl, theProfileName); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public FhirContext getFhirContext() { + if (myChain.size() == 0) { + return null; + } + return myChain.get(0).getFhirContext(); + } + + /** + * Add a validation support module to the chain. + *

        + * Note that this method is not thread-safe. All validation support modules should be added prior to use. + *

        + * + * @param theValidationSupport The validation support. Must not be null, and must have a {@link #getFhirContext() FhirContext} that is configured for the same FHIR version as other entries in the chain. + */ + public void addValidationSupport(IValidationSupport theValidationSupport) { + int index = myChain.size(); + addValidationSupport(index, theValidationSupport); + } + + /** + * Add a validation support module to the chain at the given index. + *

        + * Note that this method is not thread-safe. All validation support modules should be added prior to use. + *

        + * + * @param theIndex The index to add to + * @param theValidationSupport The validation support. Must not be null, and must have a {@link #getFhirContext() FhirContext} that is configured for the same FHIR version as other entries in the chain. + */ + public void addValidationSupport(int theIndex, IValidationSupport theValidationSupport) { + Validate.notNull(theValidationSupport, "theValidationSupport must not be null"); + + if (theValidationSupport.getFhirContext() == null) { + String message = "Can not add validation support: getFhirContext() returns null"; + throw new ConfigurationException(message); + } + + FhirContext existingFhirContext = getFhirContext(); + if (existingFhirContext != null) { + FhirVersionEnum newVersion = theValidationSupport.getFhirContext().getVersion().getVersion(); + FhirVersionEnum existingVersion = existingFhirContext.getVersion().getVersion(); + if (!existingVersion.equals(newVersion)) { + String message = "Trying to add validation support of version " + newVersion + " to chain with " + myChain.size() + " entries of version " + existingVersion; + throw new ConfigurationException(message); + } + } + + myChain.add(theIndex, theValidationSupport); + } + + @Override + public ValueSetExpansionOutcome expandValueSet(IValidationSupport theRootValidationSupport, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { + for (IValidationSupport next : myChain) { + // TODO: test if code system is supported? + ValueSetExpansionOutcome expanded = next.expandValueSet(theRootValidationSupport, null, theValueSetToExpand); + if (expanded != null) { + return expanded; + } + } + return null; + } + + @Override + public List fetchAllConformanceResources() { + List retVal = new ArrayList<>(); + for (IValidationSupport next : myChain) { + List candidates = next.fetchAllConformanceResources(); + if (candidates != null) { + retVal.addAll(candidates); + } + } + return retVal; + } + + @Override + public List fetchAllStructureDefinitions() { + ArrayList retVal = new ArrayList<>(); + Set urls = new HashSet<>(); + for (IValidationSupport nextSupport : myChain) { + List allStructureDefinitions = nextSupport.fetchAllStructureDefinitions(); + if (allStructureDefinitions != null) { + for (IBaseResource next : allStructureDefinitions) { + + IPrimitiveType urlType = getFhirContext().newTerser().getSingleValueOrNull(next, "url", IPrimitiveType.class); + if (urlType == null || isBlank(urlType.getValueAsString()) || urls.add(urlType.getValueAsString())) { + retVal.add(next); + } + } + } + } + return retVal; + } + + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + for (IValidationSupport next : myChain) { + IBaseResource retVal = next.fetchCodeSystem(theSystem); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public IBaseResource fetchValueSet(String theUrl) { + for (IValidationSupport next : myChain) { + IBaseResource retVal = next.fetchValueSet(theUrl); + if (retVal != null) { + return retVal; + } + } + return null; + } + + + @Override + public T fetchResource(Class theClass, String theUri) { + for (IValidationSupport next : myChain) { + T retVal = next.fetchResource(theClass, theUri); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public IBaseResource fetchStructureDefinition(String theUrl) { + for (IValidationSupport next : myChain) { + IBaseResource retVal = next.fetchStructureDefinition(theUrl); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public boolean isCodeSystemSupported(IValidationSupport theRootValidationSupport, String theSystem) { + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theRootValidationSupport, theSystem)) { + return true; + } + } + return false; + } + + @Override + public CodeValidationResult validateCode(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + for (IValidationSupport next : myChain) { + if (theOptions.isInferSystem() || (theCodeSystem != null && next.isCodeSystemSupported(theRootValidationSupport, theCodeSystem))) { + CodeValidationResult retVal = next.validateCode(theRootValidationSupport, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl); + if (retVal != null) { + return retVal; + } + } + } + return null; + } + + @Override + public CodeValidationResult validateCodeInValueSet(IValidationSupport theRootValidationSupport, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + for (IValidationSupport next : myChain) { + CodeValidationResult retVal = next.validateCodeInValueSet(theRootValidationSupport, theOptions, theCodeSystem, theCode, theDisplay, theValueSet); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public LookupCodeResult lookupCode(IValidationSupport theRootValidationSupport, String theSystem, String theCode) { + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theRootValidationSupport, theSystem)) { + return next.lookupCode(theRootValidationSupport, theSystem, theCode); + } + } + return null; + } + + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/BaseValidatorBridge.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java similarity index 96% rename from hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/BaseValidatorBridge.java rename to hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java index 55475941eb7..f72dcb8daf8 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/BaseValidatorBridge.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.r5.hapi.validation; +package org.hl7.fhir.common.hapi.validation.validator; import ca.uhn.fhir.validation.IValidationContext; import ca.uhn.fhir.validation.IValidatorModule; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java new file mode 100644 index 00000000000..804c22a5164 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java @@ -0,0 +1,396 @@ +package org.hl7.fhir.common.hapi.validation.validator; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.validation.IInstanceValidatorModule; +import ca.uhn.fhir.validation.IValidationContext; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.convertors.VersionConvertor_10_50; +import org.hl7.fhir.convertors.VersionConvertor_14_50; +import org.hl7.fhir.convertors.VersionConvertor_30_50; +import org.hl7.fhir.convertors.VersionConvertor_40_50; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.TypeDetails; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings({"PackageAccessibility", "Duplicates"}) +public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule { + + private boolean myAnyExtensionsAllowed = true; + private BestPracticeWarningLevel myBestPracticeWarningLevel; + private IValidationSupport myValidationSupport; + private boolean noTerminologyChecks = false; + private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext; + private boolean errorForUnknownProfiles; + private boolean assumeValidRestReferences; + private List myExtensionDomains = Collections.emptyList(); + private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; + private volatile FhirContext myDstu2Context; + private volatile FhirContext myHl7OrgDstu2Context; + + /** + * Constructor + *

        + * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} + */ + public FhirInstanceValidator(FhirContext theContext) { + this(theContext.getValidationSupport()); + } + + /** + * Constructor which uses the given validation support + * + * @param theValidationSupport The validation support + */ + public FhirInstanceValidator(IValidationSupport theValidationSupport) { + if (theValidationSupport.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) { + myValidationSupport = new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupport); + } else { + myValidationSupport = theValidationSupport; + } + } + + /** + * Every element in a resource or data type includes an optional extension child element + * which is identified by it's {@code url attribute}. There exists a number of predefined + * extension urls or extension domains:

          + *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • + *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • + *
        + * It is possible to extend this list of known extension by defining custom extensions: + * Any url which starts which one of the elements in the list of custom extension domains is + * considered as known. + *

        + * Any unknown extension domain will result in an information message when validating a resource. + *

        + */ + public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) { + this.myExtensionDomains = extensionDomains; + return this; + } + + /** + * Every element in a resource or data type includes an optional extension child element + * which is identified by it's {@code url attribute}. There exists a number of predefined + * extension urls or extension domains:
          + *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • + *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • + *
        + * It is possible to extend this list of known extension by defining custom extensions: + * Any url which starts which one of the elements in the list of custom extension domains is + * considered as known. + *

        + * Any unknown extension domain will result in an information message when validating a resource. + *

        + */ + public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { + this.myExtensionDomains = Arrays.asList(extensionDomains); + return this; + } + + /** + * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). + *

        + * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is + * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be + * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice + * guielines will be ignored. + *

        + * + * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel) + */ + public BestPracticeWarningLevel getBestPracticeWarningLevel() { + return myBestPracticeWarningLevel; + } + + /** + * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at + * this level. + *

        + * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is + * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be + * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice + * guielines will be ignored. + *

        + * + * @param theBestPracticeWarningLevel The level, must not be null + */ + public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { + Validate.notNull(theBestPracticeWarningLevel); + myBestPracticeWarningLevel = theBestPracticeWarningLevel; + } + + /** + * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of + * DefaultProfileValidationSupport if the no-arguments constructor for this object was used. + * + * @return + */ + public IValidationSupport getValidationSupport() { + return myValidationSupport; + } + + /** + * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of + * DefaultProfileValidationSupport if the no-arguments constructor for this object was used. + */ + public void setValidationSupport(IValidationSupport theValidationSupport) { + myValidationSupport = theValidationSupport; + myWrappedWorkerContext = null; + } + + /** + * If set to {@literal true} (default is true) extensions which are not known to the + * validator (e.g. because they have not been explicitly declared in a profile) will + * be validated but will not cause an error. + */ + public boolean isAnyExtensionsAllowed() { + return myAnyExtensionsAllowed; + } + + /** + * If set to {@literal true} (default is true) extensions which are not known to the + * validator (e.g. because they have not been explicitly declared in a profile) will + * be validated but will not cause an error. + */ + public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { + myAnyExtensionsAllowed = theAnyExtensionsAllowed; + } + + public boolean isErrorForUnknownProfiles() { + return errorForUnknownProfiles; + } + + public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { + this.errorForUnknownProfiles = errorForUnknownProfiles; + } + + /** + * If set to {@literal true} (default is false) the valueSet will not be validate + */ + public boolean isNoTerminologyChecks() { + return noTerminologyChecks; + } + + /** + * If set to {@literal true} (default is false) the valueSet will not be validate + */ + public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { + noTerminologyChecks = theNoTerminologyChecks; + } + + public List getExtensionDomains() { + return myExtensionDomains; + } + + @Override + protected List validate(IValidationContext theValidationCtx) { + VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; + if (wrappedWorkerContext == null) { + VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter; + + switch (myValidationSupport.getFhirContext().getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: { + converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { + @Override + public Resource toCanonical(IBaseResource theNonCanonical) { + IBaseResource nonCanonical = theNonCanonical; + Resource retVal = VersionConvertor_10_50.convertResource((org.hl7.fhir.dstu2.model.Resource) nonCanonical); + if (nonCanonical instanceof org.hl7.fhir.dstu2.model.ValueSet) { + org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical; + if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) { + if (!valueSet.hasCompose()) { + org.hl7.fhir.r5.model.ValueSet valueSetR5 = (org.hl7.fhir.r5.model.ValueSet) retVal; + valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem()); + } + } + } + return retVal; + } + + @Override + public IBaseResource fromCanonical(Resource theCanonical) { + IBaseResource canonical = VersionConvertor_10_50.convertResource(theCanonical); + return canonical; + } + }; + break; + } + + case DSTU2_1: { + converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { + @Override + public org.hl7.fhir.r5.model.Resource toCanonical(IBaseResource theNonCanonical) { + return VersionConvertor_14_50.convertResource((org.hl7.fhir.dstu2016may.model.Resource) theNonCanonical); + } + + @Override + public IBaseResource fromCanonical(org.hl7.fhir.r5.model.Resource theCanonical) { + return VersionConvertor_14_50.convertResource(theCanonical); + } + }; + break; + } + + case DSTU3: { + converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { + @Override + public Resource toCanonical(IBaseResource theNonCanonical) { + return VersionConvertor_30_50.convertResource((org.hl7.fhir.dstu3.model.Resource) theNonCanonical, true); + } + + @Override + public IBaseResource fromCanonical(Resource theCanonical) { + return VersionConvertor_30_50.convertResource(theCanonical, true); + } + }; + break; + } + + case R4: { + converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { + @Override + public org.hl7.fhir.r5.model.Resource toCanonical(IBaseResource theNonCanonical) { + return VersionConvertor_40_50.convertResource((org.hl7.fhir.r4.model.Resource) theNonCanonical); + } + + @Override + public IBaseResource fromCanonical(org.hl7.fhir.r5.model.Resource theCanonical) { + return VersionConvertor_40_50.convertResource(theCanonical); + } + }; + break; + } + + case R5: { + converter = VersionSpecificWorkerContextWrapper.IDENTITY_VERSION_TYPE_CONVERTER; + break; + } + + default: + throw new IllegalStateException(); + } + + wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(myValidationSupport, converter); + } + myWrappedWorkerContext = wrappedWorkerContext; + + return new ValidatorWrapper() + .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) + .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) + .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) + .setExtensionDomains(getExtensionDomains()) + .setNoTerminologyChecks(isNoTerminologyChecks()) + .setValidatorResourceFetcher(getValidatorResourceFetcher()) + .setAssumeValidRestReferences(isAssumeValidRestReferences()) + .validate(wrappedWorkerContext, theValidationCtx); + } + + private FhirContext getDstu2Context() { + FhirContext dstu2Context = myDstu2Context; + if (dstu2Context == null) { + dstu2Context = FhirContext.forDstu2(); + myDstu2Context = dstu2Context; + } + return dstu2Context; + } + + private FhirContext getHl7OrgDstu2Context() { + FhirContext hl7OrgDstu2Context = myHl7OrgDstu2Context; + if (hl7OrgDstu2Context == null) { + hl7OrgDstu2Context = FhirContext.forDstu2Hl7Org(); + myHl7OrgDstu2Context = hl7OrgDstu2Context; + } + return hl7OrgDstu2Context; + } + + public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { + return validatorResourceFetcher; + } + + public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { + this.validatorResourceFetcher = validatorResourceFetcher; + } + + public boolean isAssumeValidRestReferences() { + return assumeValidRestReferences; + } + + public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { + this.assumeValidRestReferences = assumeValidRestReferences; + } + + /** + * Clear any cached data held by the validator or any of its internal stores. This is mostly intended + * for unit tests, but could be used for production uses too. + */ + public void invalidateCaches() { + myValidationSupport.invalidateCaches(); + myWrappedWorkerContext.invalidateCaches(); + } + + + public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext { + @Override + public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { + return null; + } + + @Override + public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { + return null; + } + + @Override + public boolean log(String argument, List focus) { + return false; + } + + @Override + public FunctionDetails resolveFunction(String functionName) { + return null; + } + + @Override + public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { + return null; + } + + @Override + public List executeFunction(Object appContext, String functionName, List> parameters) { + return null; + } + + @Override + public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { + return null; + } + + @Override + public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { + return false; + } + + @Override + public ValueSet resolveValueSet(Object appContext, String url) { + return null; + } + } + + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java new file mode 100644 index 00000000000..26a005207e3 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java @@ -0,0 +1,75 @@ +package org.hl7.fhir.common.hapi.validation.validator; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.IValidationSupport; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.common.hapi.validation.support.BaseValidationSupportWrapper; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; +import java.util.stream.Collectors; + +public class HapiToHl7OrgDstu2ValidatingSupportWrapper extends BaseValidationSupportWrapper implements IValidationSupport { + private final FhirContext myHapiCtx; + + /** + * Constructor + */ + public HapiToHl7OrgDstu2ValidatingSupportWrapper(IValidationSupport theWrap) { + super(FhirContext.forDstu2Hl7Org(), theWrap); + + Validate.isTrue(theWrap.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2); + myHapiCtx = theWrap.getFhirContext(); + } + + @Override + public List fetchAllConformanceResources() { + return super.fetchAllConformanceResources(); + } + + @Override + public List fetchAllStructureDefinitions() { + return super + .fetchAllStructureDefinitions() + .stream() + .map(t -> translate(t)) + .collect(Collectors.toList()); + } + + @Override + public T fetchResource(Class theClass, String theUri) { + Class type = translateTypeToHapi(theClass); + IBaseResource output = super.fetchResource(type, theUri); + return theClass.cast(translate(output)); + } + + @Override + public IBaseResource fetchCodeSystem(String theSystem) { + IBaseResource output = super.fetchCodeSystem(theSystem); + return translate(output); + } + + @Override + public IBaseResource fetchValueSet(String theUri) { + return translate(super.fetchValueSet(theUri)); + } + + @Override + public IBaseResource fetchStructureDefinition(String theUrl) { + return translate(super.fetchStructureDefinition(theUrl)); + } + + private Class translateTypeToHapi(Class theCodeSystemType) { + String resName = getFhirContext().getResourceDefinition(theCodeSystemType).getName(); + return myHapiCtx.getResourceDefinition(resName).getImplementingClass(); + } + + private IBaseResource translate(IBaseResource theInput) { + if (theInput == null) { + return null; + } + String encoded = myHapiCtx.newJsonParser().encodeResourceToString(theInput); + return getFhirContext().newJsonParser().parseResource(encoded); + } +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ProfileKnowledgeWorkerR5.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ProfileKnowledgeWorkerR5.java new file mode 100644 index 00000000000..d4bfea7ce0e --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ProfileKnowledgeWorkerR5.java @@ -0,0 +1,69 @@ +package org.hl7.fhir.common.hapi.validation.validator; + +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.StructureDefinition; + +public class ProfileKnowledgeWorkerR5 implements org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider { + private final FhirContext myCtx; + + public ProfileKnowledgeWorkerR5(FhirContext theCtx) { + myCtx = theCtx; + } + + @Override + public boolean isDatatype(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); + } + + @Override + public boolean isResource(String typeSimple) { + BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); + Validate.notNull(typeSimple); + return def instanceof RuntimeResourceDefinition; + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return false; + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + return null; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition theStructureDefinition, ElementDefinition.ElementDefinitionBindingComponent theElementDefinitionBindingComponent, String theS) throws FHIRException { + return null; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition theStructureDefinition, String theS, String theS1) throws FHIRException { + return null; + } + + @Override + public String getLinkForProfile(StructureDefinition theStructureDefinition, String theS) { + return null; + } + + @Override + public boolean prependLinks() { + return false; + } + + @Override + public String getLinkForUrl(String corePath, String url) { + throw new UnsupportedOperationException(); + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java similarity index 92% rename from hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java rename to hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java index 37323be9e1c..243fb93814e 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/ValidatorWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.common.hapi.validation; +package org.hl7.fhir.common.hapi.validation.validator; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -17,8 +17,8 @@ import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.r5.validation.InstanceValidator; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.InstanceValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -30,7 +30,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class ValidatorWrapper { +class ValidatorWrapper { private static final Logger ourLog = LoggerFactory.getLogger(ValidatorWrapper.class); private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel; @@ -90,7 +90,7 @@ public class ValidatorWrapper { public List validate(IWorkerContext theWorkerContext, IValidationContext theValidationContext) { InstanceValidator v; - FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext(); + FHIRPathEngine.IEvaluationContext evaluationCtx = new FhirInstanceValidator.NullEvaluationContext(); try { v = new InstanceValidator(theWorkerContext, evaluationCtx); } catch (Exception e) { @@ -171,11 +171,18 @@ public class ValidatorWrapper { for (int i = 0; i < messages.size(); i++) { ValidationMessage next = messages.get(i); String message = next.getMessage(); + + // TODO: are these still needed? if ("Binding has no source, so can't be checked".equals(message) || "ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) { messages.remove(i); i--; } + + if (message.endsWith("\" could not be resolved, so has not been checked") && next.getLevel() == ValidationMessage.IssueSeverity.WARNING) { + next.setLevel(ValidationMessage.IssueSeverity.ERROR); + } + } return messages; @@ -183,7 +190,10 @@ public class ValidatorWrapper { private void fetchAndAddProfile(IWorkerContext theWorkerContext, List theProfileStructureDefinitions, String theUrl) throws org.hl7.fhir.exceptions.FHIRException { try { - StructureDefinition structureDefinition = theWorkerContext.fetchResourceWithException(StructureDefinition.class, theUrl); + + // NOTE: We expect the following call to generate a snapshot if needed + StructureDefinition structureDefinition = theWorkerContext.fetchRawProfile(theUrl); + theProfileStructureDefinitions.add(structureDefinition); } catch (FHIRException e) { ourLog.debug("Failed to load profile: {}", theUrl); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java new file mode 100644 index 00000000000..c48c6121b82 --- /dev/null +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -0,0 +1,578 @@ +package org.hl7.fhir.common.hapi.validation.validator; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.time.DateUtils; +import org.fhir.ucum.UcumService; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.TerminologyServiceException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.formats.IParser; +import org.hl7.fhir.r5.formats.ParserType; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.terminologies.ValueSetExpander; +import org.hl7.fhir.r5.utils.INarrativeGenerator; +import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.utilities.TranslationServices; +import org.hl7.fhir.utilities.i18n.I18nBase; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationOptions; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext { + public static final IVersionTypeConverter IDENTITY_VERSION_TYPE_CONVERTER = new VersionTypeConverterR5(); + private static FhirContext ourR5Context = FhirContext.forR5(); + private final IValidationSupport myValidationSupport; + private final IVersionTypeConverter myModelConverter; + private volatile List myAllStructures; + private LoadingCache myFetchResourceCache; + private org.hl7.fhir.r5.model.Parameters myExpansionProfile; + + public VersionSpecificWorkerContextWrapper(IValidationSupport theValidationSupport, IVersionTypeConverter theModelConverter) { + myValidationSupport = theValidationSupport; + myModelConverter = theModelConverter; + + long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; + if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { + timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); + } + + myFetchResourceCache = Caffeine.newBuilder() + .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) + .maximumSize(10000) + .build(key -> { + + String fetchResourceName = key.getResourceName(); + if (myValidationSupport.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) { + if ("CodeSystem".equals(fetchResourceName)) { + fetchResourceName = "ValueSet"; + } + } + Class fetchResourceType = myValidationSupport.getFhirContext().getResourceDefinition(fetchResourceName).getImplementingClass(); + IBaseResource fetched = myValidationSupport.fetchResource(fetchResourceType, key.getUri()); + + if (fetched == null) { + return null; + } + + + Resource canonical = myModelConverter.toCanonical(fetched); + + if (canonical instanceof StructureDefinition) { + if (((StructureDefinition) canonical).getSnapshot().isEmpty()) { + fetched = myValidationSupport.generateSnapshot(myValidationSupport, fetched, "", null, ""); + canonical = myModelConverter.toCanonical(fetched); + } + } + + return canonical; + }); + + setValidationMessageLanguage(getLocale()); + } + + @Override + public List allConformanceResources() { + throw new UnsupportedOperationException(); + } + + @Override + public String getLinkForUrl(String corePath, String url) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getBinaries() { + return null; + } + + @Override + public void generateSnapshot(StructureDefinition input) throws FHIRException { + if (input.hasSnapshot()) { + return; + } + + org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(ourR5Context); + ArrayList messages = new ArrayList<>(); + org.hl7.fhir.r5.model.StructureDefinition base = (org.hl7.fhir.r5.model.StructureDefinition) fetchResource(StructureDefinition.class, input.getBaseDefinition()); + if (base == null) { + throw new PreconditionFailedException("Unknown base definition: " + input.getBaseDefinition()); + } + new org.hl7.fhir.r5.conformance.ProfileUtilities(this, messages, profileKnowledgeProvider).generateSnapshot(base, input, "", null, ""); + + } + + @Override + public void generateSnapshot(StructureDefinition theStructureDefinition, boolean theB) { + // nothing yet + } + + @Override + public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { + return myExpansionProfile; + } + + @Override + public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { + myExpansionProfile = expParameters; + } + + @Override + public List allStructures() { + + List retVal = myAllStructures; + if (retVal == null) { + retVal = new ArrayList<>(); + for (IBaseResource next : myValidationSupport.fetchAllStructureDefinitions()) { + try { + Resource converted = myModelConverter.toCanonical(next); + retVal.add((StructureDefinition) converted); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + } + myAllStructures = retVal; + } + + return retVal; + } + + @Override + public List getStructures() { + return allStructures(); + } + + @Override + public void cacheResource(Resource res) { + throw new UnsupportedOperationException(); + } + + @Nonnull + private ValidationResult convertValidationResult(@Nullable IValidationSupport.CodeValidationResult theResult) { + ValidationResult retVal = null; + if (theResult != null) { + String code = theResult.getCode(); + String display = theResult.getDisplay(); + String issueSeverity = theResult.getSeverityCode(); + String message = theResult.getMessage(); + if (isNotBlank(code)) { + retVal = new ValidationResult(new org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent() + .setCode(code) + .setDisplay(display)); + } else if (isNotBlank(issueSeverity)) { + retVal = new ValidationResult(ValidationMessage.IssueSeverity.fromCode(issueSeverity), message, ValueSetExpander.TerminologyServiceErrorClass.UNKNOWN); + } + + } + + if (retVal == null) { + retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed"); + } + + return retVal; + } + + @Override + public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { + IBaseResource convertedSource; + try { + convertedSource = myModelConverter.fromCanonical(source); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupport.expandValueSet(myValidationSupport, null, convertedSource); + + org.hl7.fhir.r5.model.ValueSet convertedResult = null; + if (expanded.getValueSet() != null) { + try { + convertedResult = (ValueSet) myModelConverter.toCanonical(expanded.getValueSet()); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + } + + String error = expanded.getError(); + ValueSetExpander.TerminologyServiceErrorClass result = null; + + return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result); + } + + @Override + public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean Hierarchical) { + throw new UnsupportedOperationException(); + } + + @Override + public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { + throw new UnsupportedOperationException(); + } + + @Override + public Locale getLocale() { + return myValidationSupport.getFhirContext().getLocalizer().getLocale(); + } + + @Override + public void setLocale(Locale locale) { + // ignore + } + + @Override + public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { + IBaseResource fetched = myValidationSupport.fetchCodeSystem(system); + if (fetched == null) { + return null; + } + try { + return (org.hl7.fhir.r5.model.CodeSystem) myModelConverter.toCanonical(fetched); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + } + + @Override + public T fetchResource(Class class_, String uri) { + + if (isBlank(uri)) { + return null; + } + + ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); + @SuppressWarnings("unchecked") + T retVal = (T) myFetchResourceCache.get(key); + + return retVal; + } + + @Override + public Resource fetchResourceById(String type, String uri) { + throw new UnsupportedOperationException(); + } + + @Override + public T fetchResourceWithException(Class class_, String uri) throws FHIRException { + T retVal = fetchResource(class_, uri); + if (retVal == null) { + throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); + } + return retVal; + } + + @Override + public List findMapsForSource(String url) { + throw new UnsupportedOperationException(); + } + + @Override + public String getAbbreviation(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { + throw new UnsupportedOperationException(); + } + + @Override + public IParser getParser(ParserType type) { + throw new UnsupportedOperationException(); + } + + @Override + public IParser getParser(String type) { + throw new UnsupportedOperationException(); + } + + @Override + public List getResourceNames() { + return new ArrayList<>(myValidationSupport.getFhirContext().getResourceNames()); + } + + @Override + public Set getResourceNamesAsSet() { + return myValidationSupport.getFhirContext().getResourceNames(); + } + + @Override + public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { + throw new UnsupportedOperationException(); + } + + @Override + public String getOverrideVersionNs() { + return null; + } + + @Override + public void setOverrideVersionNs(String value) { + + } + + @Override + public StructureDefinition fetchTypeDefinition(String typeName) { + return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); + } + + @Override + public StructureDefinition fetchRawProfile(String url) { + StructureDefinition retVal = fetchResource(StructureDefinition.class, url); + + if (retVal != null && retVal.getSnapshot().isEmpty()) { + generateSnapshot(retVal); + } + + return retVal; + } + + @Override + public List getTypeNames() { + throw new UnsupportedOperationException(); + } + + @Override + public UcumService getUcumService() { + throw new UnsupportedOperationException(); + } + + @Override + public void setUcumService(UcumService ucumService) { + throw new UnsupportedOperationException(); + } + + @Override + public String getVersion() { + return myValidationSupport.getFhirContext().getVersion().getVersion().getFhirVersionString(); + } + + @Override + public boolean hasCache() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasResource(Class class_, String uri) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNoTerminologyServer() { + return false; + } + + @Override + public List listTransforms() { + throw new UnsupportedOperationException(); + } + + @Override + public IParser newJsonParser() { + throw new UnsupportedOperationException(); + } + + @Override + public IResourceValidator newValidator() { + throw new UnsupportedOperationException(); + } + + @Override + public IParser newXmlParser() { + throw new UnsupportedOperationException(); + } + + @Override + public String oid2Uri(String code) { + throw new UnsupportedOperationException(); + } + + @Override + public ILoggingService getLogger() { + return null; + } + + @Override + public void setLogger(ILoggingService logger) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean supportsSystem(String system) { + return myValidationSupport.isCodeSystemSupported(myValidationSupport, system); + } + + @Override + public TranslationServices translator() { + throw new UnsupportedOperationException(); + } + + @Override + public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) { + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myValidationSupport, convertConceptValidationOptions(theOptions), system, code, display, null); + return convertValidationResult(result); + } + + @Override + public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String display, org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myModelConverter.fromCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(theOptions), theSystem, theCode, display, convertedVs); + return convertValidationResult(result); + } + + @Override + public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + try { + if (theValueSet != null) { + convertedVs = myModelConverter.fromCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(theOptions).setInferSystem(true), null, code, null, convertedVs); + return convertValidationResult(result); + } + + @Override + public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myModelConverter.fromCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + + IValidationSupport.CodeValidationResult result = myValidationSupport.validateCodeInValueSet(myValidationSupport, convertConceptValidationOptions(theOptions), code.getSystem(), code.getCode(), code.getDisplay(), convertedVs); + return convertValidationResult(result); + } + + @Override + public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet theVs) { + for (Coding next : code.getCoding()) { + ValidationResult retVal = validateCode(theOptions, next, theVs); + if (retVal.isOk()) { + return retVal; + } + } + + return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null); + } + + public void invalidateCaches() { + myFetchResourceCache.invalidateAll(); + } + + public interface IVersionTypeConverter { + + org.hl7.fhir.r5.model.Resource toCanonical(IBaseResource theNonCanonical); + + IBaseResource fromCanonical(org.hl7.fhir.r5.model.Resource theCanonical); + + } + + private static class ResourceKey { + private final int myHashCode; + private String myResourceName; + private String myUri; + + private ResourceKey(String theResourceName, String theUri) { + myResourceName = theResourceName; + myUri = theUri; + myHashCode = new HashCodeBuilder(17, 37) + .append(myResourceName) + .append(myUri) + .toHashCode(); + } + + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + ResourceKey that = (ResourceKey) theO; + + return new EqualsBuilder() + .append(myResourceName, that.myResourceName) + .append(myUri, that.myUri) + .isEquals(); + } + + public String getResourceName() { + return myResourceName; + } + + public String getUri() { + return myUri; + } + + @Override + public int hashCode() { + return myHashCode; + } + } + + private static class VersionTypeConverterR5 implements IVersionTypeConverter { + @Override + public Resource toCanonical(IBaseResource theNonCanonical) { + return (Resource) theNonCanonical; + } + + @Override + public IBaseResource fromCanonical(Resource theCanonical) { + return theCanonical; + } + } + + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; + } + +} + + + diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/FhirInstanceValidator.java deleted file mode 100644 index e4ab01d7f78..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/FhirInstanceValidator.java +++ /dev/null @@ -1,799 +0,0 @@ -package org.hl7.fhir.dstu2016may.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.validation.IValidationContext; -import ca.uhn.fhir.validation.IValidatorModule; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.time.DateUtils; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.common.hapi.validation.ValidatorWrapper; -import org.hl7.fhir.convertors.VersionConvertor_14_50; -import org.hl7.fhir.convertors.conv14_50.CodeSystem14_50; -import org.hl7.fhir.convertors.conv14_50.StructureDefinition14_50; -import org.hl7.fhir.convertors.conv14_50.ValueSet14_50; -import org.hl7.fhir.dstu2016may.model.CodeSystem; -import org.hl7.fhir.dstu2016may.model.CodeableConcept; -import org.hl7.fhir.dstu2016may.model.Coding; -import org.hl7.fhir.dstu2016may.model.ImplementationGuide; -import org.hl7.fhir.dstu2016may.model.Questionnaire; -import org.hl7.fhir.dstu2016may.model.StructureDefinition; -import org.hl7.fhir.dstu2016may.model.ValueSet; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.TerminologyServiceException; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.formats.IParser; -import org.hl7.fhir.r5.formats.ParserType; -import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.model.Resource; -import org.hl7.fhir.r5.utils.INarrativeGenerator; -import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.utilities.TranslationServices; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationOptions; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class); - - private boolean myAnyExtensionsAllowed = true; - private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel; - private StructureDefinition myStructureDefintion; - private IValidationSupport myValidationSupport; - private boolean noTerminologyChecks = false; - private volatile WorkerContextWrapper myWrappedWorkerContext; - - private boolean errorForUnknownProfiles; - private List myExtensionDomains = Collections.emptyList(); - - /** - * Constructor - *

        - * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} - */ - public FhirInstanceValidator() { - this(new DefaultProfileValidationSupport()); - } - - /** - * Constructor which uses the given validation support - * - * @param theValidationSupport The validation support - */ - public FhirInstanceValidator(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:

          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) { - this.myExtensionDomains = extensionDomains; - return this; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:
          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { - this.myExtensionDomains = Arrays.asList(extensionDomains); - return this; - } - - private String determineResourceName(Document theDocument) { - NodeList list = theDocument.getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i) instanceof Element) { - return list.item(i).getLocalName(); - } - } - return theDocument.getDocumentElement().getLocalName(); - } - - private ArrayList determineIfProfilesSpecified(Document theDocument) { - ArrayList profileNames = new ArrayList(); - NodeList list = theDocument.getChildNodes().item(0).getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) { - NodeList metaList = list.item(i).getChildNodes(); - for (int j = 0; j < metaList.getLength(); j++) { - if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) { - profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue()); - } - } - break; - } - } - return profileNames; - } - - private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) { - String sdName = null; - try { - // Test if a URL was passed in specifying the structure definition and test if "StructureDefinition" is part of the URL - URL testIfUrl = new URL(resourceName); - sdName = resourceName; - } catch (MalformedURLException e) { - sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName; - } - StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName); - return profile; - } - - public void flushCaches() { - myWrappedWorkerContext = null; - } - - /** - * Returns the "best practice" warning level (default is {@link IResourceValidator.BestPracticeWarningLevel#Hint}). - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link IResourceValidator.BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link IResourceValidator.BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - */ - public IResourceValidator.BestPracticeWarningLevel getBestPracticeWarningLevel() { - return myBestPracticeWarningLevel; - } - - /** - * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at - * this level. - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link IResourceValidator.BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link IResourceValidator.BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @param theBestPracticeWarningLevel The level, must not be null - */ - public void setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel theBestPracticeWarningLevel) { - Validate.notNull(theBestPracticeWarningLevel); - myBestPracticeWarningLevel = theBestPracticeWarningLevel; - } - - /** - * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public IValidationSupport getValidationSupport() { - return myValidationSupport; - } - - /** - * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public void setValidationSupport(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - myWrappedWorkerContext = null; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public boolean isAnyExtensionsAllowed() { - return myAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { - myAnyExtensionsAllowed = theAnyExtensionsAllowed; - } - - public boolean isErrorForUnknownProfiles() { - return errorForUnknownProfiles; - } - - public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { - this.errorForUnknownProfiles = errorForUnknownProfiles; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public boolean isNoTerminologyChecks() { - return noTerminologyChecks; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { - noTerminologyChecks = theNoTerminologyChecks; - } - - public void setStructureDefintion(StructureDefinition theStructureDefintion) { - myStructureDefintion = theStructureDefintion; - } - - private List getExtensionDomains() { - return myExtensionDomains; - } - - @Override - protected List validate(IValidationContext theValidationCtx) { - final FhirContext ctx = theValidationCtx.getFhirContext(); - - WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; - if (wrappedWorkerContext == null) { - HapiWorkerContext workerContext = new HapiWorkerContext(ctx, myValidationSupport); - wrappedWorkerContext = new WorkerContextWrapper(workerContext); - } - myWrappedWorkerContext = wrappedWorkerContext; - - return new ValidatorWrapper() - .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) - .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) - .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) - .setExtensionDomains(getExtensionDomains()) - .setNoTerminologyChecks(isNoTerminologyChecks()) - .validate(wrappedWorkerContext, theValidationCtx); - - } - - - private static class WorkerContextWrapper implements IWorkerContext { - private final HapiWorkerContext myWrap; - private volatile List myAllStructures; - private LoadingCache myFetchResourceCache; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - WorkerContextWrapper(HapiWorkerContext theWorkerContext) { - myWrap = theWorkerContext; - - long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; - if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { - timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); - } - - myFetchResourceCache = Caffeine.newBuilder() - .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) - .maximumSize(10000) - .build(key -> { - org.hl7.fhir.dstu2016may.model.Resource fetched; - switch (key.getResourceName()) { - case "StructureDefinition": - fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri()); - break; - case "ValueSet": - fetched = myWrap.fetchResource(ValueSet.class, key.getUri()); - break; - case "CodeSystem": - fetched = myWrap.fetchResource(CodeSystem.class, key.getUri()); - break; - case "Questionnaire": - fetched = myWrap.fetchResource(Questionnaire.class, key.getUri()); - break; - case "ImplementationGuide": - fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri()); - break; - default: - throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName()); - } - - if (fetched == null) { - return null; - } - - try { - if (fetched instanceof StructureDefinition) { - return convert((StructureDefinition) fetched); - } - return VersionConvertor_14_50.convertResource(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - }); - } - - @Override - public List allConformanceResources() { - throw new UnsupportedOperationException(); - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition theStructureDefinition) throws DefinitionException, FHIRException { - // nothing yet - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public Map getBinaries() { - throw new UnsupportedOperationException(); - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition p, boolean theb) throws FHIRException { - // nothing yet - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - @Override - public List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (StructureDefinition next : myWrap.allStructures()) { - try { - retVal.add(convert(next)); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - public org.hl7.fhir.r5.model.StructureDefinition convert(StructureDefinition next) { - org.hl7.fhir.r5.model.StructureDefinition structureDefinition = StructureDefinition14_50.convertStructureDefinition(next); - if (next.getDerivation() != org.hl7.fhir.dstu2016may.model.StructureDefinition.TypeDerivationRule.CONSTRAINT) { - structureDefinition.setType(next.getName()); - } - return structureDefinition; - } - - @Override - public List getStructures() { - return allStructures(); - } - - @Override - public void cacheResource(org.hl7.fhir.r5.model.Resource res) { - throw new UnsupportedOperationException(); - } - - @Nonnull - private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - ValidationMessage.IssueSeverity issueSeverity = ValidationMessage.IssueSeverity.fromCode(theResult.getSeverity().toCode()); - String message = theResult.getMessage(); - org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null; - if (theResult.asConceptDefinition() != null) { - try { - conceptDefinition = CodeSystem14_50.convertConceptDefinitionComponent(theResult.asConceptDefinition()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - retVal = new ValidationResult(issueSeverity, message, conceptDefinition); - } - - if (retVal == null) { - retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed"); - } - - return retVal; - } - - @Override - public org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) { - ValueSet convertedSource; - try { - convertedSource = ValueSet14_50.convertValueSet(source); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, false); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueset() != null) { - try { - convertedResult = ValueSet14_50.convertValueSet(expanded.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - String error = expanded.getError(); - - return new org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, null); - } - - @Override - public org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) { - throw new UnsupportedOperationException(); - } - - @Override - public org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { - ValueSet.ConceptSetComponent convertedInc = null; - if (inc != null) { - try { - convertedInc = ValueSet14_50.convertConceptSetComponent(inc); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSet.ValueSetExpansionComponent expansion = myWrap.expandVS(convertedInc); - org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent valueSetExpansionComponent = null; - if (expansion != null) { - try { - valueSetExpansionComponent = ValueSet14_50.convertValueSetExpansionComponent(expansion); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - org.hl7.fhir.r5.model.ValueSet vsc = new org.hl7.fhir.r5.model.ValueSet(); - vsc.setExpansion(valueSetExpansionComponent); - org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome outcome2 = new org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome(vsc); - return outcome2; - } - - @Override - public ValidationResult validateCode(ValidationOptions options, String system, String code, String display) { - return null; - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - CodeSystem fetched = myWrap.fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return CodeSystem14_50.convertCodeSystem(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - @Override - public T fetchResource(Class class_, String uri) { - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public org.hl7.fhir.r5.model.Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public List findMapsForSource(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String name) { - return myWrap.getAbbreviation(name); - } - - @Override - public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String type) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - return myWrap.getResourceNames(); - } - - @Override - public Set getResourceNamesAsSet() { - return new HashSet<>(myWrap.getResourceNames()); - } - - @Override - public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getOverrideVersionNs() { - return null; - } - - @Override - public void setOverrideVersionNs(String value) { - - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchRawProfile(String url) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, url); - } - - - @Override - public List getTypeNames() { - return myWrap.getResourceNames(); - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType type) { - throw new UnsupportedOperationException(); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(); - } - - @Override - public String getVersion() { - return "1.4"; - } - - @Override - public boolean hasCache() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNoTerminologyServer() { - throw new UnsupportedOperationException(); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(); - } - - @Override - public List listTransforms() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public String oid2Uri(String code) { - return myWrap.oid2Uri(code); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supportsSystem(String system) { - return myWrap.supportsSystem(system); - } - - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - - try { - if (vs != null) { - convertedVs = ValueSet14_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - try { - if (vs != null) { - convertedVs = ValueSet14_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet vs) { - Coding convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_14_50.convertCoding(code); - } - if (vs != null) { - convertedVs = ValueSet14_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet vs) { - CodeableConcept convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_14_50.convertCodeableConcept(code); - } - if (vs != null) { - convertedVs = ValueSet14_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - } - - private static class ResourceKey { - private final int myHashCode; - private String myResourceName; - private String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/BaseValidatorBridge.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/BaseValidatorBridge.java deleted file mode 100644 index 0cdc2411681..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/BaseValidatorBridge.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import ca.uhn.fhir.validation.*; - -/** - * Base class for a bridge between the RI validation tools and HAPI - */ -abstract class BaseValidatorBridge implements IValidatorModule { - - public BaseValidatorBridge() { - super(); - } - - private void doValidate(IValidationContext theCtx) { - List messages = validate(theCtx); - - for (ValidationMessage riMessage : messages) { - SingleValidationMessage hapiMessage = new SingleValidationMessage(); - if (riMessage.getCol() != -1) { - hapiMessage.setLocationCol(riMessage.getCol()); - } - if (riMessage.getLine() != -1) { - hapiMessage.setLocationLine(riMessage.getLine()); - } - hapiMessage.setLocationString(riMessage.getLocation()); - hapiMessage.setMessage(riMessage.getMessage()); - if (riMessage.getLevel() != null) { - hapiMessage.setSeverity(ResultSeverityEnum.fromCode(riMessage.getLevel().toCode())); - } - theCtx.addValidationMessage(hapiMessage); - } - } - - protected abstract List validate(IValidationContext theCtx); - - @Override - public void validateResource(IValidationContext theCtx) { - doValidate(theCtx); - } - -} \ No newline at end of file diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java deleted file mode 100644 index 4eda95a6cb3..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import static org.apache.commons.lang3.StringUtils.defaultIfBlank; - -@SuppressWarnings("unchecked") -public class CachingValidationSupport implements IValidationSupport { - - private static final Logger ourLog = LoggerFactory.getLogger(CachingValidationSupport.class); - private final IValidationSupport myWrap; - private final Cache myCache; - - public CachingValidationSupport(IValidationSupport theWrap) { - myWrap = theWrap; - myCache = Caffeine.newBuilder().expireAfterWrite(60, TimeUnit.SECONDS).build(); - } - - @Override - public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return myWrap.expandValueSet(theContext, theInclude); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return loadFromCache("fetchAllConformanceResources", - t -> myWrap.fetchAllConformanceResources(theContext)); - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return loadFromCache("fetchAllStructureDefinitions", - t -> myWrap.fetchAllStructureDefinitions(theContext)); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myWrap.fetchCodeSystem(theContext, uri); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myWrap.fetchValueSet(theContext, uri); - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return loadFromCache("fetchResource " + theClass.getName() + " " + theUri, - t -> myWrap.fetchResource(theContext, theClass, theUri)); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return myWrap.fetchStructureDefinition(theCtx, theUrl); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return myWrap.isCodeSystemSupported(theContext, theSystem); - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS"); - return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl)); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - return myWrap.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return myWrap.lookupCode(theContext, theSystem, theCode); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return myWrap.generateSnapshot(theInput, theUrl, theName); - } - - @Nullable - private T loadFromCache(String theKey, Function theLoader) { - ourLog.trace("Loading: {}", theKey); - Function> loaderWrapper = key -> { - ourLog.trace("Loading {} from cache", theKey); - return Optional.ofNullable(theLoader.apply(theKey)); - }; - Optional result = (Optional) myCache.get(theKey, loaderWrapper); - return result.orElse(null); - } - - public void flushCaches() { - myCache.invalidateAll(); - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java deleted file mode 100644 index 609941ba6a9..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java +++ /dev/null @@ -1,827 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidationContext; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.time.DateUtils; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.common.hapi.validation.ValidatorWrapper; -import org.hl7.fhir.convertors.VersionConvertor_30_50; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeableConcept; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.ImplementationGuide; -import org.hl7.fhir.dstu3.model.Questionnaire; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.dstu3.model.SearchParameter; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.TerminologyServiceException; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.formats.IParser; -import org.hl7.fhir.r5.formats.ParserType; -import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.utils.INarrativeGenerator; -import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; -import org.hl7.fhir.utilities.TranslationServices; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.utilities.validation.ValidationOptions; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static org.hl7.fhir.convertors.conv30_50.CodeSystem30_50.convertCodeSystem; -import static org.hl7.fhir.convertors.conv30_50.CodeSystem30_50.convertConceptDefinitionComponent; -import static org.hl7.fhir.convertors.conv30_50.StructureDefinition30_50.convertStructureDefinition; -import static org.hl7.fhir.convertors.conv30_50.ValueSet30_50.convertConceptSetComponent; -import static org.hl7.fhir.convertors.conv30_50.ValueSet30_50.convertValueSet; -import static org.hl7.fhir.convertors.conv30_50.ValueSet30_50.convertValueSetExpansionComponent; - -@SuppressWarnings({"PackageAccessibility", "Duplicates"}) -public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class); - - private boolean myAnyExtensionsAllowed = true; - private BestPracticeWarningLevel myBestPracticeWarningLevel; - private StructureDefinition myStructureDefintion; - private IValidationSupport myValidationSupport; - private boolean noTerminologyChecks = false; - private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; - private volatile WorkerContextWrapper myWrappedWorkerContext; - - private boolean errorForUnknownProfiles; - private List myExtensionDomains = Collections.emptyList(); - private boolean assumeValidRestReferences; - - /** - * Constructor - *

        - * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} - */ - public FhirInstanceValidator() { - this(new DefaultProfileValidationSupport()); - } - - /** - * Constructor which uses the given validation support - * - * @param theValidationSupport The validation support - */ - public FhirInstanceValidator(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:

          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) { - this.myExtensionDomains = extensionDomains; - return this; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:
          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { - this.myExtensionDomains = Arrays.asList(extensionDomains); - return this; - } - - private String determineResourceName(Document theDocument) { - NodeList list = theDocument.getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i) instanceof Element) { - return list.item(i).getLocalName(); - } - } - return theDocument.getDocumentElement().getLocalName(); - } - - private ArrayList determineIfProfilesSpecified(Document theDocument) { - ArrayList profileNames = new ArrayList(); - NodeList list = theDocument.getChildNodes().item(0).getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) { - NodeList metaList = list.item(i).getChildNodes(); - for (int j = 0; j < metaList.getLength(); j++) { - if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) { - profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue()); - } - } - break; - } - } - return profileNames; - } - - private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) { - String sdName = null; - try { - // Test if a URL was passed in specifying the structure definition and test if "StructureDefinition" is part of the URL - URL testIfUrl = new URL(resourceName); - sdName = resourceName; - } catch (MalformedURLException e) { - sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName; - } - StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName); - return profile; - } - - public void flushCaches() { - myWrappedWorkerContext = null; - } - - /** - * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel) - */ - public BestPracticeWarningLevel getBestPracticeWarningLevel() { - return myBestPracticeWarningLevel; - } - - /** - * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at - * this level. - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @param theBestPracticeWarningLevel The level, must not be null - */ - public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { - Validate.notNull(theBestPracticeWarningLevel); - myBestPracticeWarningLevel = theBestPracticeWarningLevel; - } - - /** - * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public IValidationSupport getValidationSupport() { - return myValidationSupport; - } - - /** - * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public void setValidationSupport(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - myWrappedWorkerContext = null; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public boolean isAnyExtensionsAllowed() { - return myAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { - myAnyExtensionsAllowed = theAnyExtensionsAllowed; - } - - public boolean isErrorForUnknownProfiles() { - return errorForUnknownProfiles; - } - - public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { - this.errorForUnknownProfiles = errorForUnknownProfiles; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public boolean isNoTerminologyChecks() { - return noTerminologyChecks; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { - noTerminologyChecks = theNoTerminologyChecks; - } - - public void setStructureDefintion(StructureDefinition theStructureDefintion) { - myStructureDefintion = theStructureDefintion; - } - - private List getExtensionDomains() { - return myExtensionDomains; - } - - @Override - protected List validate(IValidationContext theValidationCtx) { - final FhirContext ctx = theValidationCtx.getFhirContext(); - - WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; - if (wrappedWorkerContext == null) { - HapiWorkerContext workerContext = new HapiWorkerContext(ctx, myValidationSupport); - wrappedWorkerContext = new WorkerContextWrapper(workerContext); - } - myWrappedWorkerContext = wrappedWorkerContext; - - return new ValidatorWrapper() - .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) - .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) - .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) - .setExtensionDomains(getExtensionDomains()) - .setNoTerminologyChecks(isNoTerminologyChecks()) - .setValidatorResourceFetcher(getValidatorResourceFetcher()) - .setAssumeValidRestReferences(isAssumeValidRestReferences()) - .validate(wrappedWorkerContext, theValidationCtx); - - } - - public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { - return validatorResourceFetcher; - } - - public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { - this.validatorResourceFetcher = validatorResourceFetcher; - } - - public boolean isAssumeValidRestReferences() { - return assumeValidRestReferences; - } - - public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { - this.assumeValidRestReferences = assumeValidRestReferences; - } - - - private static class WorkerContextWrapper implements IWorkerContext { - private final HapiWorkerContext myWrap; - private final VersionConvertor_30_50 myConverter; - private volatile List myAllStructures; - private LoadingCache myFetchResourceCache; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - WorkerContextWrapper(HapiWorkerContext theWorkerContext) { - myWrap = theWorkerContext; - myConverter = new VersionConvertor_30_50(); - - long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; - if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { - timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); - } - - myFetchResourceCache = Caffeine.newBuilder() - .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) - .maximumSize(10000) - .build(key -> { - Resource fetched; - switch (key.getResourceName()) { - case "StructureDefinition": - fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri()); - break; - case "ValueSet": - fetched = myWrap.fetchResource(ValueSet.class, key.getUri()); - break; - case "CodeSystem": - fetched = myWrap.fetchResource(CodeSystem.class, key.getUri()); - break; - case "Questionnaire": - fetched = myWrap.fetchResource(Questionnaire.class, key.getUri()); - break; - case "ImplementationGuide": - fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri()); - break; - case "SearchParameter": - fetched = myWrap.fetchResource(SearchParameter.class, key.getUri()); - break; - default: - throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName()); - } - - if (fetched == null) { - return null; - } - - try { - return VersionConvertor_30_50.convertResource(fetched, true); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - }); - } - - @Override - public List allConformanceResources() { - throw new UnsupportedOperationException(); - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public Map getBinaries() { - return null; - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition p) throws FHIRException { - // nothing yet - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition theStructureDefinition, boolean theB) { - // nothing yet - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - @Override - public List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (StructureDefinition next : myWrap.allStructures()) { - try { - retVal.add(convertStructureDefinition(next)); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public List getStructures() { - return allStructures(); - } - - @Override - public void cacheResource(org.hl7.fhir.r5.model.Resource res) { - throw new UnsupportedOperationException(); - } - - @Nonnull - private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - IssueSeverity issueSeverity = theResult.getSeverity(); - String message = theResult.getMessage(); - org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null; - if (theResult.asConceptDefinition() != null) { - try { - conceptDefinition = convertConceptDefinitionComponent(theResult.asConceptDefinition()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - retVal = new ValidationResult(issueSeverity, message, conceptDefinition); - } - - if (retVal == null) { - retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed"); - } - - return retVal; - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) { - ValueSet convertedSource; - try { - convertedSource = convertValueSet(source); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueset() != null) { - try { - convertedResult = convertValueSet(expanded.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - String error = expanded.getError(); - ValueSetExpander.TerminologyServiceErrorClass result = null; - - return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { - ValueSet.ConceptSetComponent convertedInc = null; - if (inc != null) { - try { - convertedInc = convertConceptSetComponent(inc); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSet.ValueSetExpansionComponent expansion = myWrap.expandVS(convertedInc, heirarchical); - org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent valueSetExpansionComponent = null; - if (expansion != null) { - try { - valueSetExpansionComponent = convertValueSetExpansionComponent(expansion); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSetExpander.ValueSetExpansionOutcome outcome = new ValueSetExpander.ValueSetExpansionOutcome(new org.hl7.fhir.r5.model.ValueSet()); - outcome.getValueset().setExpansion(valueSetExpansionComponent); - return outcome; - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - CodeSystem fetched = myWrap.fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return convertCodeSystem(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - @Override - public T fetchResource(Class class_, String uri) { - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public org.hl7.fhir.r5.model.Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public List findMapsForSource(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String name) { - return myWrap.getAbbreviation(name); - } - - public VersionConvertor_30_50 getConverter() { - return myConverter; - } - - @Override - public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType type) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String type) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - return myWrap.getResourceNames(); - } - - @Override - public Set getResourceNamesAsSet() { - return new HashSet<>(myWrap.getResourceNames()); - } - - @Override - public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getOverrideVersionNs() { - return null; - } - - @Override - public void setOverrideVersionNs(String value) { - - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchRawProfile(String url) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, url); - } - - @Override - public List getTypeNames() { - return myWrap.getTypeNames(); - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(); - } - - @Override - public String getVersion() { - return myWrap.getVersion(); - } - - @Override - public boolean hasCache() { - return myWrap.hasCache(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNoTerminologyServer() { - return myWrap.isNoTerminologyServer(); - } - - @Override - public List listTransforms() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public String oid2Uri(String code) { - return myWrap.oid2Uri(code); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supportsSystem(String system) { - return myWrap.supportsSystem(system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) { - org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - - try { - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - try { - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet vs) { - Coding convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_30_50.convertCoding(code); - } - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet vs) { - CodeableConcept convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_30_50.convertCodeableConcept(code); - } - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - } - - private static class ResourceKey { - private final int myHashCode; - private String myResourceName; - private String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java deleted file mode 100644 index 1e1e4f176d0..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.MetadataResource; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -/** - * This class is an implementation of {@link IValidationSupport} which may be pre-populated - * with a collection of validation resources to be used by the validator. - */ -public class PrePopulatedValidationSupport implements IValidationSupport { - - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; - - /** - * Constructor - */ - public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap<>(); - myValueSets = new HashMap<>(); - myCodeSystems = new HashMap<>(); - } - - /** - * Constructor - * - * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and - * values are the resource itself. - * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - */ - public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { - myStructureDefinitions = theStructureDefinitions; - myValueSets = theValueSets; - myCodeSystems = theCodeSystems; - } - - /** - * Add a new CodeSystem resource which will be available to the validator. Note that - * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addCodeSystem(CodeSystem theCodeSystem) { - Validate.notBlank(theCodeSystem.getUrl(), "theCodeSystem.getUrl() must not return a value"); - addToMap(theCodeSystem, myCodeSystems, theCodeSystem.getUrl()); - } - - /** - * Add a new StructureDefinition resource which will be available to the validator. Note that - * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addStructureDefinition(StructureDefinition theStructureDefinition) { - Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value"); - addToMap(theStructureDefinition, myStructureDefinitions, theStructureDefinition.getUrl()); - } - - private void addToMap(T theStructureDefinition, Map map, String theUrl) { - if (isNotBlank(theUrl)) { - map.put(theUrl, theStructureDefinition); - - int lastSlashIdx = theUrl.lastIndexOf('/'); - if (lastSlashIdx != -1) { - map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition); - int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1); - if (previousSlashIdx != -1) { - map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition); - } - } - - } - } - - /** - * Add a new ValueSet resource which will be available to the validator. Note that - * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addValueSet(ValueSet theValueSet) { - Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value"); - addToMap(theValueSet, myValueSets, theValueSet.getUrl()); - } - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - retVal.addAll(myCodeSystems.values()); - retVal.addAll(myStructureDefinitions.values()); - retVal.addAll(myValueSets.values()); - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList<>(myStructureDefinitions.values()); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myCodeSystems.get(uri); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myValueSets.get(uri); - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) myStructureDefinitions.get(theUri); - } - if (theClass.equals(ValueSet.class)) { - return (T) myValueSets.get(theUri); - } - if (theClass.equals(CodeSystem.class)) { - return (T) myCodeSystems.get(theUri); - } - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return myStructureDefinitions.get(theUrl); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) { - return null; - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/SnapshotGeneratingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/SnapshotGeneratingValidationSupport.java deleted file mode 100644 index 2c13ae643e7..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/SnapshotGeneratingValidationSupport.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.conformance.ProfileUtilities; -import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.ElementDefinition; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simple validation support module that handles profile snapshot generation. This is - * separate from other funcrtions since it needs a link to a validation support - * module itself, and it is useful to be able to pass a chain in. - */ -public class SnapshotGeneratingValidationSupport implements IValidationSupport { - private final FhirContext myCtx; - private final IValidationSupport myValidationSupport; - - public SnapshotGeneratingValidationSupport(FhirContext theCtx, IValidationSupport theValidationSupport) { - Validate.notNull(theCtx); - Validate.notNull(theValidationSupport); - myCtx = theCtx; - myValidationSupport = theValidationSupport; - } - - @Override - public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return null; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) { - IWorkerContext context = new HapiWorkerContext(myCtx, myValidationSupport); - ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorker(); - ArrayList messages = new ArrayList<>(); - - StructureDefinition base = myValidationSupport.fetchStructureDefinition(myCtx, theInput.getBaseDefinition()); - if (base == null) { - throw new PreconditionFailedException("Unknown base definition: " + theInput.getBaseDefinition()); - } - - new ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, theInput, theUrl, theProfileName); - - return theInput; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - private class MyProfileKnowledgeWorker implements ProfileUtilities.ProfileKnowledgeProvider { - @Override - public boolean isDatatype(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); - } - - @Override - public boolean isResource(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return def instanceof RuntimeResourceDefinition; - } - - @Override - public boolean hasLinkFor(String typeSimple) { - return false; - } - - @Override - public String getLinkFor(String corePath, String typeSimple) { - return null; - } - - @Override - public BindingResolution resolveBinding(StructureDefinition def, ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException { - return null; - } - - @Override - public String getLinkForProfile(StructureDefinition profile, String url) { - return null; - } - - @Override - public boolean prependLinks() { - return false; - } - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java deleted file mode 100644 index 9cc2432ca0d..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class ValidationSupportChain implements IValidationSupport { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationSupportChain.class); - - private List myChain; - - /** - * Constructor - */ - public ValidationSupportChain() { - myChain = new ArrayList<>(); - } - - /** - * Constructor - */ - public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { - this(); - for (IValidationSupport next : theValidationSupportModules) { - if (next != null) { - myChain.add(next); - } - } - } - - public void addValidationSupport(IValidationSupport theValidationSupport) { - myChain.add(theValidationSupport); - } - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - for (IValidationSupport next : myChain) { - if (isNotBlank(theInclude.getSystem())) { - if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { - ValueSetExpansionComponent expansion = next.expandValueSet(theCtx, theInclude); - if (expansion != null) { - return expansion; - } - } - } - ValueSetExpansionComponent retVal = next.expandValueSet(theCtx, theInclude); - if (retVal != null && retVal.getContains().size() > 0) { - return retVal; - } - } - return myChain.get(0).expandValueSet(theCtx, theInclude); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - List retVal = new ArrayList<>(); - for (IValidationSupport next : myChain) { - List candidates = next.fetchAllConformanceResources(theContext); - if (candidates != null) { - retVal.addAll(candidates); - } - } - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList(); - Set urls = new HashSet<>(); - for (IValidationSupport nextSupport : myChain) { - List list = nextSupport.fetchAllStructureDefinitions(theContext); - if (list != null) { - for (StructureDefinition next : list) { - if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { - retVal.add(next); - } - } - } - } - return retVal; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String uri) { - for (IValidationSupport next : myChain) { - ValueSet retVal = next.fetchValueSet(theCtx, uri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - for (IValidationSupport next : myChain) { - T retVal = next.fetchResource(theContext, theClass, theUri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - for (IValidationSupport next : myChain) { - StructureDefinition retVal = next.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theSystem)) { - return true; - } - } - return false; - } - - @Override - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - - ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size()); - - for (IValidationSupport next : myChain) { - if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) { - CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - if (result != null) { - ourLog.debug("Chain item {} returned outcome {}", next, result.isOk()); - return result; - } - } else { - ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem); - } - } - return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - CodeValidationResult retVal = null; - for (IValidationSupport next : myChain) { - retVal = next.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - if (retVal != null) { - break; - } - } - return retVal; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theContext, theSystem)) { - return next.lookupCode(theContext, theSystem, theCode); - } - } - return null; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) { - StructureDefinition outcome = null; - for (org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport next : myChain) { - outcome = next.generateSnapshot(theInput, theUrl, theProfileName); - if (outcome != null) { - break; - } - } - return outcome; - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/BaseValidatorBridge.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/BaseValidatorBridge.java deleted file mode 100644 index 502102f37f7..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/BaseValidatorBridge.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import ca.uhn.fhir.validation.*; - -/** - * Base class for a bridge between the RI validation tools and HAPI - */ -abstract class BaseValidatorBridge implements IValidatorModule { - - public BaseValidatorBridge() { - super(); - } - - private void doValidate(IValidationContext theCtx) { - List messages = validate(theCtx); - - for (ValidationMessage riMessage : messages) { - SingleValidationMessage hapiMessage = new SingleValidationMessage(); - if (riMessage.getCol() != -1) { - hapiMessage.setLocationCol(riMessage.getCol()); - } - if (riMessage.getLine() != -1) { - hapiMessage.setLocationLine(riMessage.getLine()); - } - hapiMessage.setLocationString(riMessage.getLocation()); - hapiMessage.setMessage(riMessage.getMessage()); - if (riMessage.getLevel() != null) { - hapiMessage.setSeverity(ResultSeverityEnum.fromCode(riMessage.getLevel().toCode())); - } - theCtx.addValidationMessage(hapiMessage); - } - } - - protected abstract List validate(IValidationContext theCtx); - - @Override - public void validateResource(IValidationContext theCtx) { - doValidate(theCtx); - } - -} \ No newline at end of file diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/CachingValidationSupport.java deleted file mode 100644 index f202393a24f..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/CachingValidationSupport.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("unchecked") -public class CachingValidationSupport implements IValidationSupport { - - private final IValidationSupport myWrap; - private final Cache myCache; - - public CachingValidationSupport(IValidationSupport theWrap) { - myWrap = theWrap; - myCache = Caffeine.newBuilder().expireAfterWrite(60, TimeUnit.SECONDS).build(); - } - - - @Override - public List allStructures() { - return (List) myCache.get("fetchAllStructureDefinitions", - t -> myWrap.allStructures()); - } - - @Override - public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return myWrap.expandValueSet(theContext, theInclude); - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { - return myWrap.fetchCodeSystem(theContext, theSystem); - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return myWrap.fetchResource(theContext, theClass, theUri); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return myWrap.isCodeSystemSupported(theContext, theSystem); - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - return myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay); - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/DefaultProfileValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/DefaultProfileValidationSupport.java deleted file mode 100644 index 50be9aa2c3f..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/DefaultProfileValidationSupport.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu2.model.*; -import org.hl7.fhir.dstu2.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class DefaultProfileValidationSupport implements IValidationSupport { - - private Map myDefaultValueSets; - private Map myCodeSystems; - private static final Set ourResourceNames; - private static final FhirContext ourHl7OrgCtx; - - static { - ourHl7OrgCtx = FhirContext.forDstu2Hl7Org(); - ourResourceNames = FhirContext.forDstu2().getResourceNames(); - } - - /** - * Constructor - */ - public DefaultProfileValidationSupport() { - super(); - } - - @Override - public List allStructures() { - ArrayList retVal = new ArrayList<>(); - - for (String next : ourResourceNames) { - StructureDefinition profile = FhirInstanceValidator.loadProfileOrReturnNull(null, ourHl7OrgCtx, next); - retVal.add(profile); - } - - return retVal; - } - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - return null; - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { - synchronized (this) { - Map valueSets = myCodeSystems; - if (valueSets == null) { - valueSets = new HashMap<>(); - - loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/valuesets.xml"); - loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml"); - loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml"); - - myCodeSystems = valueSets; - } - - return valueSets.get(theSystem); - } - } - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { - return (T) FhirInstanceValidator.loadProfileOrReturnNull(null, theContext, theUri.substring("http://hl7.org/fhir/StructureDefinition/".length())); - } - if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) { - Map defaultValueSets = myDefaultValueSets; - if (defaultValueSets == null) { - String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml"; - InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path); - if (valuesetText == null) { - return null; - } - InputStreamReader reader; - try { - reader = new InputStreamReader(valuesetText, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // Shouldn't happen! - throw new InternalErrorException("UTF-8 encoding not supported on this platform", e); - } - - defaultValueSets = new HashMap<>(); - - FhirContext ctx = FhirInstanceValidator.getHl7OrgDstu2Ctx(theContext); - Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - IdType nextId = new IdType(next.getFullUrl()); - if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) { - continue; - } - defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource()); - } - - myDefaultValueSets = defaultValueSets; - } - - return (T) defaultValueSets.get(theUri); - } - - return null; - } - - public void flush() { - myDefaultValueSets = null; - myCodeSystems = null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - private void loadValueSets(FhirContext theContext, Map theValueSets, String theFile) { - InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theFile); - try { - if (valuesetText != null) { - InputStreamReader reader = null; - try { - reader = new InputStreamReader(valuesetText, "UTF-8"); - - FhirContext ctx = FhirInstanceValidator.getHl7OrgDstu2Ctx(theContext); - Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, reader); - for (BundleEntryComponent next : bundle.getEntry()) { - ValueSet nextValueSet = (ValueSet) next.getResource(); - String system = nextValueSet.getCodeSystem().getSystem(); - if (isNotBlank(system)) { - theValueSets.put(system, nextValueSet); - } - } - - } catch (UnsupportedEncodingException e) { - // Shouldn't happen! - throw new InternalErrorException("UTF-8 encoding not supported on this platform", e); - } finally { - IOUtils.closeQuietly(reader); - } - - } - } finally { - IOUtils.closeQuietly(valuesetText); - } - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - if (Constants.codeSystemNotNeeded(theCodeSystem)) { - return null; - } - - ValueSet vs = fetchCodeSystem(theContext, theCodeSystem); - if (vs != null) { - for (ValueSet.ConceptDefinitionComponent nextConcept : vs.getCodeSystem().getConcept()) { - if (nextConcept.getCode().equals(theCode)) { - ValueSet.ConceptDefinitionComponent component = new ValueSet.ConceptDefinitionComponent(new CodeType(theCode)); - return new CodeValidationResult(component); - } - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidator.java deleted file mode 100644 index 877c9cfa880..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidator.java +++ /dev/null @@ -1,988 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.util.XmlUtil; -import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidationContext; -import com.github.benmanes.caffeine.cache.CacheLoader; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.converter.NullVersionConverterAdvisor50; -import org.hl7.fhir.convertors.VersionConvertorAdvisor50; -import org.hl7.fhir.convertors.VersionConvertor_10_50; -import org.hl7.fhir.convertors.conv10_50.ValueSet10_50; -import org.hl7.fhir.convertors.conv14_50.CodeSystem14_50; -import org.hl7.fhir.dstu2.model.CodeableConcept; -import org.hl7.fhir.dstu2.model.Coding; -import org.hl7.fhir.dstu2.model.Questionnaire; -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.TerminologyServiceException; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.formats.IParser; -import org.hl7.fhir.r5.formats.ParserType; -import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.Parameters; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.INarrativeGenerator; -import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; -import org.hl7.fhir.r5.utils.IResourceValidator.IdStatus; -import org.hl7.fhir.r5.validation.InstanceValidator; -import org.hl7.fhir.utilities.TranslationServices; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.utilities.validation.ValidationOptions; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.hl7.fhir.convertors.VersionConvertor_10_50.convertCoding; -import static org.hl7.fhir.convertors.conv10_50.StructureDefinition10_50.convertStructureDefinition; -import static org.hl7.fhir.convertors.conv10_50.ValueSet10_50.convertConceptSetComponent; -import static org.hl7.fhir.convertors.conv10_50.ValueSet10_50.convertValueSet; -import static org.hl7.fhir.convertors.conv10_50.ValueSet10_50.convertValueSetExpansionComponent; - -public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class); - private static final FhirContext FHIR_CONTEXT = FhirContext.forDstu2(); - private static FhirContext ourHl7OrgCtx; - - private boolean myAnyExtensionsAllowed = true; - private BestPracticeWarningLevel myBestPracticeWarningLevel; - private StructureDefinition myStructureDefintion; - private IValidationSupport myValidationSupport; - private boolean noTerminologyChecks = false; - - public boolean isAssumeValidRestReferences() { - return assumeValidRestReferences; - } - - public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { - this.assumeValidRestReferences = assumeValidRestReferences; - } - - private boolean assumeValidRestReferences; - private volatile WorkerContextWrapper myWrappedWorkerContext; - private VersionConvertorAdvisor50 myAdvisor = new NullVersionConverterAdvisor50(); - private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; - - /** - * Constructor - *

        - * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} - */ - public FhirInstanceValidator() { - this(new DefaultProfileValidationSupport()); - } - - /** - * Constructor which uses the given validation support - * - * @param theValidationSupport The validation support - */ - public FhirInstanceValidator(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - } - - private CodeSystem convertCodeSystem(ValueSet theFetched) { - CodeSystem retVal = new CodeSystem(); - - retVal.setUrl(theFetched.getCodeSystem().getSystem()); - retVal.setVersion(theFetched.getVersion()); - - List sourceConceptList = theFetched.getCodeSystem().getConcept(); - List targetConceptList = retVal.getConcept(); - convertConceptList(sourceConceptList, targetConceptList); - - return retVal; - } - - private CodeSystem.ConceptDefinitionComponent convertConceptDefinition(ValueSet.ConceptDefinitionComponent next) { - CodeSystem.ConceptDefinitionComponent convertedConceptDef = new CodeSystem.ConceptDefinitionComponent(); - convertedConceptDef.setCode(next.getCode()); - convertedConceptDef.setDisplay(next.getDisplay()); - - convertConceptList(next.getConcept(), convertedConceptDef.getConcept()); - return convertedConceptDef; - } - - private void convertConceptList(List theSourceConceptList, List theTargetConceptList) { - for (ValueSet.ConceptDefinitionComponent next : theSourceConceptList) { - CodeSystem.ConceptDefinitionComponent convertedConceptDef = convertConceptDefinition(next); - theTargetConceptList.add(convertedConceptDef); - } - } - - private String determineResourceName(Document theDocument) { - Element root = null; - - NodeList list = theDocument.getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i) instanceof Element) { - root = (Element) list.item(i); - break; - } - } - root = theDocument.getDocumentElement(); - return root.getLocalName(); - } - - private ArrayList determineIfProfilesSpecified(Document theDocument) - { - ArrayList profileNames = new ArrayList(); - NodeList list = theDocument.getChildNodes().item(0).getChildNodes(); - for (int i = 0; i < list.getLength(); i++) { - if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) - { - NodeList metaList = list.item(i).getChildNodes(); - for (int j = 0; j < metaList.getLength(); j++) - { - if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) - { - profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue()); - } - } - break; - } - } - return profileNames; - } - - private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) { - String sdName = null; - try { - // Test if a URL was passed in specifying the structure definition and test if "StructureDefinition" is part of the URL - URL testIfUrl = new URL(resourceName); - sdName = resourceName; - } - catch (MalformedURLException e) - { - sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName; - } - StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchResource(theCtx, StructureDefinition.class, sdName); - return profile; - } - - /** - * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)} - */ - public BestPracticeWarningLevel getBestPracticeWarningLevel() { - return myBestPracticeWarningLevel; - } - - /** - * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at - * this level. - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @param theBestPracticeWarningLevel The level, must not be null - */ - public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { - Validate.notNull(theBestPracticeWarningLevel); - myBestPracticeWarningLevel = theBestPracticeWarningLevel; - } - - /** - * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public IValidationSupport getValidationSupport() { - return myValidationSupport; - } - - /** - * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public void setValidationSupport(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - myWrappedWorkerContext = null; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public boolean isAnyExtensionsAllowed() { - return myAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { - myAnyExtensionsAllowed = theAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public boolean isNoTerminologyChecks() { - return noTerminologyChecks; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { - noTerminologyChecks = theNoTerminologyChecks; - } - - public void setStructureDefintion(StructureDefinition theStructureDefintion) { - myStructureDefintion = theStructureDefintion; - } - - protected List validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) { - - WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; - if (wrappedWorkerContext == null) { - HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport); - wrappedWorkerContext = new WorkerContextWrapper(workerContext); - } - myWrappedWorkerContext = wrappedWorkerContext; - - InstanceValidator v; - FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext(); - try { - v = new InstanceValidator(wrappedWorkerContext, evaluationCtx); - } catch (Exception e) { - throw new ConfigurationException(e); - } - - v.setBestPracticeWarningLevel(getBestPracticeWarningLevel()); - v.setAnyExtensionsAllowed(isAnyExtensionsAllowed()); - v.setResourceIdRule(IdStatus.OPTIONAL); - v.setNoTerminologyChecks(isNoTerminologyChecks()); - v.setFetcher(getValidatorResourceFetcher()); - v.setAssumeValidRestReferences(isAssumeValidRestReferences()); - - List messages = new ArrayList<>(); - - if (theEncoding == EncodingEnum.XML) { - Document document; - try { - document = XmlUtil.parseDocument(theInput); - } catch (Exception e2) { - ourLog.error("Failure to parse XML input", e2); - ValidationMessage m = new ValidationMessage(); - m.setLevel(IssueSeverity.FATAL); - m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage()); - return Collections.singletonList(m); - } - - // Determine if meta/profiles are present... - ArrayList resourceNames = determineIfProfilesSpecified(document); - if (resourceNames.isEmpty()) - { - resourceNames.add(determineResourceName(document)); - } - - for (String resourceName : resourceNames) { - StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName); - if (profile != null) { - try { - v.validate(null, messages, document, profile.getUrl()); - } catch (Exception e) { - ourLog.error("Failure during validation", e); - throw new InternalErrorException("Unexpected failure while validating resource", e); - } - } - else - { - profile = findStructureDefinitionForResourceName(theCtx, determineResourceName(document)); - if (profile != null) { - try { - v.validate(null, messages, document, profile.getUrl()); - } catch (Exception e) { - ourLog.error("Failure during validation", e); - throw new InternalErrorException("Unexpected failure while validating resource", e); - } - } - } - } - } else if (theEncoding == EncodingEnum.JSON) { - Gson gson = new GsonBuilder().create(); - JsonObject json = gson.fromJson(theInput, JsonObject.class); - - ArrayList resourceNames = new ArrayList(); - JsonArray profiles = null; - try { - profiles = json.getAsJsonObject("meta").getAsJsonArray("profile"); - for (JsonElement element : profiles) - { - resourceNames.add(element.getAsString()); - } - } catch (Exception e) { - resourceNames.add(json.get("resourceType").getAsString()); - } - - for (String resourceName : resourceNames) { - StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName); - if (profile != null) { - try { - v.validate(null, messages, json, profile.getUrl()); - } catch (Exception e) { - throw new InternalErrorException("Unexpected failure while validating resource", e); - } - } - else - { - profile = findStructureDefinitionForResourceName(theCtx, json.get("resourceType").getAsString()); - if (profile != null) { - try { - v.validate(null, messages, json, profile.getUrl()); - } catch (Exception e) { - ourLog.error("Failure during validation", e); - throw new InternalErrorException("Unexpected failure while validating resource", e); - } - } - } - } - } else { - throw new IllegalArgumentException("Unknown encoding: " + theEncoding); - } - - for (int i = 0; i < messages.size(); i++) { - ValidationMessage next = messages.get(i); - if ("Binding has no source, so can't be checked".equals(next.getMessage())) { - messages.remove(i); - i--; - } - if (next.getLocation().contains("text")) { - messages.remove(i); - i--; - } - } - return messages; - } - - public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { - return validatorResourceFetcher; - } - - public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { - this.validatorResourceFetcher = validatorResourceFetcher; - } - - @Override - protected List validate(IValidationContext theCtx) { - return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding()); - } - - static FhirContext getHl7OrgDstu2Ctx(FhirContext theCtx) { - if (theCtx.getVersion().getVersion() == FhirVersionEnum.DSTU2_HL7ORG) { - return theCtx; - } - FhirContext retVal = ourHl7OrgCtx; - if (retVal == null) { - retVal = FhirContext.forDstu2Hl7Org(); - ourHl7OrgCtx = retVal; - } - return retVal; - } - - static StructureDefinition loadProfileOrReturnNull(List theMessages, FhirContext theCtx, - String theResourceName) { - if (isBlank(theResourceName)) { - if (theMessages != null) { - theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL) - .setMessage("Could not determine resource type from request. Content appears invalid.")); - } - return null; - } - - String profileClasspath = theCtx.getVersion().getPathToSchemaDefinitions().replace("/schema", "/profile"); - String profileCpName = profileClasspath + '/' + theResourceName.toLowerCase() + ".profile.xml"; - String profileText; - try (InputStream inputStream = FhirInstanceValidator.class.getResourceAsStream(profileCpName)) { - if (inputStream == null) { - if (theMessages != null) { - theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL) - .setMessage("No profile found for resource type " + theResourceName)); - return null; - } else { - return null; - } - } - profileText = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - } catch (IOException e1) { - if (theMessages != null) { - theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL) - .setMessage("No profile found for resource type " + theResourceName)); - } - return null; - } - StructureDefinition profile = getHl7OrgDstu2Ctx(theCtx).newXmlParser().parseResource(StructureDefinition.class, - profileText); - return profile; - } - - private class WorkerContextWrapper implements IWorkerContext { - private final HapiWorkerContext myWrap; - private final VersionConvertor_10_50 myConverter; - private volatile List myAllStructures; - private LoadingCache myFetchResourceCache - = Caffeine.newBuilder() - .expireAfterWrite(10, TimeUnit.SECONDS) - .maximumSize(10000) - .build(new CacheLoader() { - @Override - public org.hl7.fhir.r5.model.Resource load(FhirInstanceValidator.ResourceKey key) throws Exception { - org.hl7.fhir.dstu2.model.Resource fetched; - switch (key.getResourceName()) { - case "StructureDefinition": - fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri()); - break; - case "CodeSystem": - case "ValueSet": - fetched = myWrap.fetchResource(ValueSet.class, key.getUri()); - - ValueSet fetchedVs = (ValueSet) fetched; - if (!fetchedVs.hasCompose()) { - if (fetchedVs.hasCodeSystem()) { - fetchedVs.getCompose().addInclude().setSystem(fetchedVs.getCodeSystem().getSystem()); - } - } - - break; - case "Questionnaire": - fetched = myWrap.fetchResource(Questionnaire.class, key.getUri()); - break; - default: - throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName()); - } - - if (fetched == null) { - if (key.getUri().equals("http://hl7.org/fhir/StructureDefinition/xhtml")) { - return null; - } - } - - try { - org.hl7.fhir.r5.model.Resource converted; - if ("CodeSystem".equals(key.getUri())) { - NullVersionConverterAdvisor50 advisor = new NullVersionConverterAdvisor50(); - converted = ValueSet10_50.convertValueSet((ValueSet) fetched, advisor); - converted = advisor.getCodeSystem((org.hl7.fhir.r5.model.ValueSet) converted); - } else { - converted = VersionConvertor_10_50.convertResource(fetched); - } - - - if (fetched instanceof StructureDefinition) { - StructureDefinition fetchedSd = (StructureDefinition) fetched; - StructureDefinition.StructureDefinitionKind kind = fetchedSd.getKind(); - if (kind == StructureDefinition.StructureDefinitionKind.DATATYPE) { - BaseRuntimeElementDefinition element = FHIR_CONTEXT.getElementDefinition(fetchedSd.getName()); - if (element instanceof RuntimePrimitiveDatatypeDefinition) { - org.hl7.fhir.r5.model.StructureDefinition convertedSd = (org.hl7.fhir.r5.model.StructureDefinition) converted; - convertedSd.setKind(org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE); - } - } - } - - return converted; - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - }); - private Parameters myExpansionProfile; - - public WorkerContextWrapper(HapiWorkerContext theWorkerContext) { - myWrap = theWorkerContext; - myConverter = new VersionConvertor_10_50(); - } - - @Override - public List allConformanceResources() { - throw new UnsupportedOperationException(); - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition p) throws FHIRException { - - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition theStructureDefinition, boolean theB) { - - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public Map getBinaries() { - return null; - } - - @Override - public Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(Parameters expParameters) { - myExpansionProfile = expParameters; - } - - @Override - public List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (StructureDefinition next : myWrap.allStructures()) { - try { - org.hl7.fhir.r5.model.StructureDefinition converted = convertStructureDefinition(next); - if (converted != null) { - retVal.add(converted); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public List getStructures() { - return allStructures(); - } - - @Override - public void cacheResource(org.hl7.fhir.r5.model.Resource res) throws FHIRException { - throw new UnsupportedOperationException(); - } - - private ValidationResult convertValidationResult(org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult theResult) { - IssueSeverity issueSeverity = theResult.getSeverity(); - String message = theResult.getMessage(); - org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null; - if (theResult.asConceptDefinition() != null) { - conceptDefinition = convertConceptDefinition(theResult.asConceptDefinition()); - } - - ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition); - return retVal; - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) { - ValueSet convertedSource = null; - try { - convertedSource = convertValueSet(source); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - org.hl7.fhir.dstu2.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueset() != null) { - try { - convertedResult = convertValueSet(expanded.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - String error = expanded.getError(); - ValueSetExpander.TerminologyServiceErrorClass result = null; - - return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { - ValueSet.ConceptSetComponent convertedInc = null; - if (inc != null) { - try { - convertedInc = convertConceptSetComponent(inc); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSet.ValueSetExpansionComponent expansion = myWrap.expandVS(convertedInc); - org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent valueSetExpansionComponent = null; - if (expansion != null) { - try { - valueSetExpansionComponent = convertValueSetExpansionComponent(expansion); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSetExpander.ValueSetExpansionOutcome outcome = new ValueSetExpander.ValueSetExpansionOutcome(new org.hl7.fhir.r5.model.ValueSet()); - outcome.getValueset().setExpansion(valueSetExpansionComponent); - return outcome; - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - ValueSet fetched = myWrap.fetchCodeSystem(system); - if (fetched == null) { - return null; - } - - return convertCodeSystem(fetched); - } - - @Override - public T fetchResource(Class class_, String uri) { - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public org.hl7.fhir.r5.model.Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public List findMapsForSource(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String name) { - return myWrap.getAbbreviation(name); - } - - public VersionConvertor_10_50 getConverter() { - return myConverter; - } - - - @Override - public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType type) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String type) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - return myWrap.getResourceNames(); - } - - @Override - public Set getResourceNamesAsSet() { - return new HashSet<>(myWrap.getResourceNames()); - } - - @Override - public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getOverrideVersionNs() { - return null; - } - - @Override - public void setOverrideVersionNs(String value) { - - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+typeName); - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchRawProfile(String url) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, url); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(); - } - - @Override - public List getTypeNames() { - throw new UnsupportedOperationException(); - } - - @Override - public String getVersion() { - return FhirVersionEnum.DSTU2.getFhirVersionString(); - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasCache() { - return false; - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNoTerminologyServer() { - return true; - } - - @Override - public List listTransforms() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() throws FHIRException { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public String oid2Uri(String code) { - throw new UnsupportedOperationException(); - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public boolean supportsSystem(String system) throws TerminologyServiceException { - return myWrap.supportsSystem(system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) { - org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - - try { - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - try { - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet vs) { - Coding convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = convertCoding(code); - } - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet vs) { - CodeableConcept convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_10_50.convertCodeableConcept(code); - } - if (vs != null) { - convertedVs = convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.dstu2.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); - return convertValidationResult(result); - } - - } - - - private static class ResourceKey { - private final int myHashCode; - private String myResourceName; - private String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContext.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContext.java deleted file mode 100644 index 1788fd582a5..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContext.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu2.formats.IParser; -import org.hl7.fhir.dstu2.formats.ParserType; -import org.hl7.fhir.instance.hapi.validation.IValidationSupport.CodeValidationResult; -import org.hl7.fhir.dstu2.model.*; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptReferenceComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.dstu2.terminologies.ValueSetExpander; -import org.hl7.fhir.dstu2.terminologies.ValueSetExpanderFactory; -import org.hl7.fhir.dstu2.terminologies.ValueSetExpanderSimple; -import org.hl7.fhir.dstu2.utils.INarrativeGenerator; -import org.hl7.fhir.dstu2.utils.IResourceValidator; -import org.hl7.fhir.dstu2.utils.IWorkerContext; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import java.io.IOException; -import java.util.List; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public final class HapiWorkerContext implements IWorkerContext, ValueSetExpanderFactory, ValueSetExpander { - private final FhirContext myCtx; - private IValidationSupport myValidationSupport; - - public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) { - myCtx = theCtx; - myValidationSupport = theValidationSupport; - } - - @Override - public List allStructures() { - return myValidationSupport.allStructures(); - } - - @Override - public StructureDefinition fetchTypeDefinition(String theUri) { - return fetchResource(StructureDefinition.class, theUri); - } - - @Override - public ValueSetExpansionOutcome expand(ValueSet theSource) throws IOException, ETooCostly { - ValueSetExpander vse = new ValueSetExpanderSimple(this, this); - ValueSetExpansionOutcome vso = vse.expand(theSource); - if (vso.getError() != null) { - return null; - } else { - return vso; - } - } - - @Override - public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc) { - return myValidationSupport.expandValueSet(myCtx, theInc); - } - - @Override - public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSet fetchCodeSystem(String theSystem) { - if (myValidationSupport == null) { - return null; - } else { - return myValidationSupport.fetchCodeSystem(myCtx, theSystem); - } - } - - @Override - public T fetchResource(Class theClass, String theUri) { - if (myValidationSupport == null) { - return null; - } else { - return myValidationSupport.fetchResource(myCtx, theClass, theUri); - } - } - - @Override - public List findMapsForSource(String theUrl) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String theName) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpander getExpander() { - return this; - } - - @Override - public INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType theType) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String theType) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasResource(Class theClass_, String theUri) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supportsSystem(String theSystem) { - if (myValidationSupport == null) { - return false; - } else { - return myValidationSupport.isCodeSystemSupported(myCtx, theSystem); - } - } - - @Override - public ValidationResult validateCode(CodeableConcept theCode, ValueSet theVs) { - for (Coding next : theCode.getCoding()) { - ValidationResult retVal = validateCode(next, theVs); - if (retVal.isOk()) { - return retVal; - } - } - - return new ValidationResult(null, null); - } - - @Override - public ValidationResult validateCode(Coding theCode, ValueSet theVs) { - String system = theCode.getSystem(); - String code = theCode.getCode(); - String display = theCode.getDisplay(); - return validateCode(system, code, display, theVs); - } - - @Override - public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) { - CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay); - if (result == null) { - return null; - } - return new ValidationResult(result.getSeverity(), result.getMessage(), result.asConceptDefinition()); - } - - @Override - public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) { - throw new UnsupportedOperationException(); - } - - @Override - public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ValueSet theVs) { - - if (Constants.codeSystemNotNeeded(theSystem) || StringUtils.equals(theSystem, theVs.getCodeSystem().getSystem())) { - for (ConceptDefinitionComponent next : theVs.getCodeSystem().getConcept()) { - ValidationResult retVal = validateCodeSystem(theCode, next); - if (retVal != null && retVal.isOk()) { - return retVal; - } - } - } - - for (ConceptSetComponent nextComposeConceptSet : theVs.getCompose().getInclude()) { - - String nextSystem = theSystem; - if (nextSystem == null && isNotBlank(nextComposeConceptSet.getSystem())) { - nextSystem = nextComposeConceptSet.getSystem(); - } - - if (Constants.codeSystemNotNeeded(theSystem) || StringUtils.equals(nextSystem, nextComposeConceptSet.getSystem())) { - for (ConceptReferenceComponent nextComposeCode : nextComposeConceptSet.getConcept()) { - ConceptDefinitionComponent conceptDef = new ConceptDefinitionComponent(); - conceptDef.setCode(nextComposeCode.getCode()); - conceptDef.setDisplay(nextComposeCode.getDisplay()); - ValidationResult retVal = validateCodeSystem(theCode, conceptDef); - if (retVal != null && retVal.isOk()) { - return retVal; - } - } - - if (nextComposeConceptSet.getConcept().isEmpty()){ - - String validateSystem = nextSystem; - if (Constants.codeSystemNotNeeded(nextSystem)) { - validateSystem = nextComposeConceptSet.getSystem(); - } - - ValidationResult result = validateCode(validateSystem, theCode, null); - if (result.isOk()){ - return result; - } - } - } - } - - return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + Constants.codeSystemWithDefaultDescription(theSystem) + "]"); - } - - private ValidationResult validateCodeSystem(String theCode, ConceptDefinitionComponent theConcept) { - if (StringUtils.equals(theCode, theConcept.getCode())) { - return new ValidationResult(theConcept); - } else { - for (ConceptDefinitionComponent next : theConcept.getConcept()) { - ValidationResult retVal = validateCodeSystem(theCode, next); - if (retVal != null && retVal.isOk()) { - return retVal; - } - } - return null; - } - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/IValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/IValidationSupport.java deleted file mode 100644 index 2885c3bbcc1..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/IValidationSupport.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; - -import ca.uhn.fhir.context.FhirContext; - -import java.util.List; - -public interface IValidationSupport { - - /** - * Fetch all structuredefinitions - */ - List allStructures(); - - /** - * Expands the given portion of a ValueSet - * - * @param theInclude - * The portion to include - * @return The expansion - */ - ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude); - - /** - * Fetch a code system by ID - * - * @param theSystem - * The code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - ValueSet fetchCodeSystem(FhirContext theContext, String theSystem); - - /** - * Loads a resource needed by the validation (a StructureDefinition, or a - * ValueSet) - * - * @param theContext - * The HAPI FHIR Context object current in use by the validator - * @param theClass - * The type of the resource to load - * @param theUri - * The resource URI - * @return Returns the resource, or null if no resource with the - * given URI can be found - */ - T fetchResource(FhirContext theContext, Class theClass, String theUri); - - /** - * Returns true if codes in the given code system can be expanded - * or validated - * - * @param theSystem - * The URI for the code system, e.g. "http://loinc.org" - * @return Returns true if codes in the given code system can be - * validated - */ - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); - - /** - * Validates that the given code exists and if possible returns a display - * name. This method is called to check codes which are found in "example" - * binding fields (e.g. Observation.code in the default profile. - * - * @param theCodeSystem - * The code system, e.g. "http://loinc.org" - * @param theCode - * The code, e.g. "1234-5" - * @param theDisplay - * The display name, if it should also be validated - * @return Returns a validation result object - */ - CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay); - - public class CodeValidationResult { - private ConceptDefinitionComponent definition; - private String message; - private IssueSeverity severity; - - public CodeValidationResult(ConceptDefinitionComponent definition) { - this.definition = definition; - } - - public CodeValidationResult(IssueSeverity severity, String message) { - this.severity = severity; - this.message = message; - } - - public CodeValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) { - this.severity = severity; - this.message = message; - this.definition = definition; - } - - public ConceptDefinitionComponent asConceptDefinition() { - return definition; - } - - public String getDisplay() { - return definition == null ? "??" : definition.getDisplay(); - } - - public String getMessage() { - return message; - } - - public IssueSeverity getSeverity() { - return severity; - } - - public boolean isOk() { - return definition != null; - } - - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/ValidationSupportChain.java deleted file mode 100644 index b416b0202e5..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/ValidationSupportChain.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.dstu2.model.StructureDefinition; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.ArrayList; -import java.util.List; - -public class ValidationSupportChain implements IValidationSupport { - - private List myChain; - - /** - * Constructor - */ - public ValidationSupportChain() { - myChain = new ArrayList(); - } - - /** - * Constructor - */ - public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { - this(); - for (IValidationSupport next : theValidationSupportModules) { - if (next != null) { - myChain.add(next); - } - } - } - - public void addValidationSupport(IValidationSupport theValidationSupport) { - myChain.add(theValidationSupport); - } - - @Override - public List allStructures() { - ArrayList retVal = new ArrayList<>(); - for (IValidationSupport next : myChain) { - retVal.addAll(next.allStructures()); - } - return retVal; - } - - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { - return next.expandValueSet(theCtx, theInclude); - } - } - return myChain.get(0).expandValueSet(theCtx, theInclude); - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - ValueSet retVal = next.fetchCodeSystem(theCtx, theSystem); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - for (IValidationSupport next : myChain) { - T retVal = next.fetchResource(theContext, theClass, theUri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theSystem)) { - return true; - } - } - return false; - } - - @Override - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { - for (IValidationSupport next : myChain) { - if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) { - return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay); - } - } - return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay); - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/WorkerContext.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/WorkerContext.java deleted file mode 100644 index c3adf2edd1e..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/hapi/validation/WorkerContext.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import org.hl7.fhir.dstu2.model.Questionnaire; -import org.hl7.fhir.dstu2.model.ValueSet; - -import java.util.HashMap; -import java.util.Map; - -public class WorkerContext { - private HashMap myValueSets = new HashMap<>(); - private HashMap myQuestionnaires = new HashMap<>(); - - public Map getValueSets() { - return myValueSets; - } - - public Map getQuestionnaires() { - return myQuestionnaires; - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/BaseValidatorBridge.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/BaseValidatorBridge.java deleted file mode 100644 index 4a86411c66f..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/BaseValidatorBridge.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import java.util.List; - -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import ca.uhn.fhir.validation.*; - -/** - * Base class for a bridge between the RI validation tools and HAPI - */ -abstract class BaseValidatorBridge implements IValidatorModule { - - public BaseValidatorBridge() { - super(); - } - - private void doValidate(IValidationContext theCtx) { - List messages = validate(theCtx); - - for (ValidationMessage riMessage : messages) { - SingleValidationMessage hapiMessage = new SingleValidationMessage(); - if (riMessage.getCol() != -1) { - hapiMessage.setLocationCol(riMessage.getCol()); - } - if (riMessage.getLine() != -1) { - hapiMessage.setLocationLine(riMessage.getLine()); - } - hapiMessage.setLocationString(riMessage.getLocation()); - hapiMessage.setMessage(riMessage.getMessage()); - if (riMessage.getLevel() != null) { - hapiMessage.setSeverity(ResultSeverityEnum.fromCode(riMessage.getLevel().toCode())); - } - theCtx.addValidationMessage(hapiMessage); - } - } - - protected abstract List validate(IValidationContext theCtx); - - @Override - public void validateResource(IValidationContext theCtx) { - doValidate(theCtx); - } - -} \ No newline at end of file diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java deleted file mode 100644 index 3945f0951c9..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import static org.apache.commons.lang3.StringUtils.defaultIfBlank; - -@SuppressWarnings("unchecked") -public class CachingValidationSupport implements IValidationSupport { - - private final IValidationSupport myWrap; - private final Cache myCache; - - public CachingValidationSupport(IValidationSupport theWrap) { - myWrap = theWrap; - myCache = Caffeine - .newBuilder() - .expireAfterWrite(60, TimeUnit.SECONDS) - .maximumSize(5000) - .build(); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return myWrap.expandValueSet(theContext, theInclude); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - String key = "fetchAllConformanceResources"; - return loadFromCache(key, t -> myWrap.fetchAllConformanceResources(theContext)); - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - String key = "fetchAllStructureDefinitions"; - return loadFromCache(key, t -> myWrap.fetchAllStructureDefinitions(theContext)); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myWrap.fetchCodeSystem(theContext, uri); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myWrap.fetchValueSet(theContext, uri); - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return loadFromCache("fetchResource " + theClass.getName() + " " + theUri, - t -> myWrap.fetchResource(theContext, theClass, theUri)); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return myWrap.fetchStructureDefinition(theCtx, theUrl); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - String key = "isCodeSystemSupported " + theSystem; - return loadFromCache(key, t -> myWrap.isCodeSystemSupported(theContext, theSystem)); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return myWrap.generateSnapshot(theInput, theUrl, theWebUrl, theProfileName); - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS"); - return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl)); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - return myWrap.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - String key = "lookupCode " + theSystem + " " + theCode; - return loadFromCache(key, t -> myWrap.lookupCode(theContext, theSystem, theCode)); - } - - @Nullable - private T loadFromCache(String theKey, Function theLoader) { - Function> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey)); - Optional result = (Optional) myCache.get(theKey, loaderWrapper); - return result.orElse(null); - } - - public void flushCaches() { - myCache.invalidateAll(); - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java deleted file mode 100644 index d5441294501..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java +++ /dev/null @@ -1,756 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidationContext; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.time.DateUtils; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.common.hapi.validation.ValidatorWrapper; -import org.hl7.fhir.convertors.VersionConvertor_40_50; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.TerminologyServiceException; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.ImplementationGuide; -import org.hl7.fhir.r4.model.Questionnaire; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.formats.IParser; -import org.hl7.fhir.r5.formats.ParserType; -import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.utils.INarrativeGenerator; -import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; -import org.hl7.fhir.utilities.TerminologyServiceOptions; -import org.hl7.fhir.utilities.TranslationServices; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.utilities.validation.ValidationOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings({"PackageAccessibility", "Duplicates"}) -public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseValidatorBridge implements IInstanceValidatorModule { - - private boolean myAnyExtensionsAllowed = true; - private BestPracticeWarningLevel myBestPracticeWarningLevel; - private IValidationSupport myValidationSupport; - private boolean noTerminologyChecks = false; - private volatile WorkerContextWrapper myWrappedWorkerContext; - private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; - private boolean assumeValidRestReferences; - private boolean errorForUnknownProfiles; - private List extensionDomains = Collections.emptyList(); - - /** - * Constructor - *

        - * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} - */ - public FhirInstanceValidator() { - this(new DefaultProfileValidationSupport()); - } - - /** - * Constructor which uses the given validation support - * - * @param theValidationSupport The validation support - */ - public FhirInstanceValidator(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:

          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) { - this.extensionDomains = extensionDomains; - return this; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:
          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { - setCustomExtensionDomains(Arrays.asList(extensionDomains)); - return this; - } - - - /** - * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel) - */ - public BestPracticeWarningLevel getBestPracticeWarningLevel() { - return myBestPracticeWarningLevel; - } - - /** - * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at - * this level. - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @param theBestPracticeWarningLevel The level, must not be null - */ - public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { - Validate.notNull(theBestPracticeWarningLevel); - myBestPracticeWarningLevel = theBestPracticeWarningLevel; - } - - /** - * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public IValidationSupport getValidationSupport() { - return myValidationSupport; - } - - /** - * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public void setValidationSupport(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - myWrappedWorkerContext = null; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public boolean isAnyExtensionsAllowed() { - return myAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { - myAnyExtensionsAllowed = theAnyExtensionsAllowed; - } - - public boolean isErrorForUnknownProfiles() { - return errorForUnknownProfiles; - } - - public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { - this.errorForUnknownProfiles = errorForUnknownProfiles; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public boolean isNoTerminologyChecks() { - return noTerminologyChecks; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { - noTerminologyChecks = theNoTerminologyChecks; - } - - @Override - protected List validate(IValidationContext theValidationCtx) { - - WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; - if (wrappedWorkerContext == null) { - HapiWorkerContext workerContext = new HapiWorkerContext(theValidationCtx.getFhirContext(), myValidationSupport); - wrappedWorkerContext = new WorkerContextWrapper(workerContext); - } - myWrappedWorkerContext = wrappedWorkerContext; - - return new ValidatorWrapper() - .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) - .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) - .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) - .setExtensionDomains(getExtensionDomains()) - .setNoTerminologyChecks(isNoTerminologyChecks()) - .setValidatorResourceFetcher(getValidatorResourceFetcher()) - .setAssumeValidRestReferences(isAssumeValidRestReferences()) - .validate(wrappedWorkerContext, theValidationCtx); - } - - public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { - return validatorResourceFetcher; - } - - public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { - this.validatorResourceFetcher = validatorResourceFetcher; - } - - - private List getExtensionDomains() { - return extensionDomains; - } - - public boolean isAssumeValidRestReferences() { - return assumeValidRestReferences; - } - - public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { - this.assumeValidRestReferences = assumeValidRestReferences; - } - - - private static class WorkerContextWrapper implements IWorkerContext { - private final HapiWorkerContext myWrap; - private volatile List myAllStructures; - private LoadingCache myFetchResourceCache; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - WorkerContextWrapper(HapiWorkerContext theWorkerContext) { - myWrap = theWorkerContext; - - long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; - if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { - timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); - } - - myFetchResourceCache = Caffeine.newBuilder() - .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) - .maximumSize(10000) - .build(key -> { - Resource fetched; - switch (key.getResourceName()) { - case "StructureDefinition": - fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri()); - break; - case "ValueSet": - fetched = myWrap.fetchResource(ValueSet.class, key.getUri()); - break; - case "CodeSystem": - fetched = myWrap.fetchResource(CodeSystem.class, key.getUri()); - break; - case "Questionnaire": - fetched = myWrap.fetchResource(Questionnaire.class, key.getUri()); - break; - case "ImplementationGuide": - fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri()); - break; - default: - throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName()); - } - - if (fetched == null) { - return null; - } - - try { - return VersionConvertor_40_50.convertResource(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - }); - } - - @Override - public List allConformanceResources() { - throw new UnsupportedOperationException(); - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition p) throws FHIRException { - // nothing yet - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition theStructureDefinition, boolean theB) { - - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - @Override - public List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (StructureDefinition next : myWrap.allStructures()) { - try { - retVal.add(org.hl7.fhir.convertors.conv40_50.StructureDefinition40_50.convertStructureDefinition(next)); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public List getStructures() { - return allStructures(); - } - - @Override - public void cacheResource(org.hl7.fhir.r5.model.Resource res) { - throw new UnsupportedOperationException(); - } - - @Nonnull - private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.r4.context.IWorkerContext.ValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - IssueSeverity issueSeverity = theResult.getSeverity(); - String message = theResult.getMessage(); - org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null; - if (theResult.asConceptDefinition() != null) { - try { - conceptDefinition = org.hl7.fhir.convertors.conv40_50.CodeSystem40_50.convertConceptDefinitionComponent(theResult.asConceptDefinition()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - retVal = new ValidationResult(issueSeverity, message, conceptDefinition); - } - - if (retVal == null) { - retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed"); - } - - return retVal; - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) { - ValueSet convertedSource; - try { - convertedSource = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(source); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueset() != null) { - try { - convertedResult = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(expanded.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - String error = expanded.getError(); - ValueSetExpander.TerminologyServiceErrorClass result = null; - - return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { - ValueSet.ConceptSetComponent convertedInc = null; - if (inc != null) { - try { - convertedInc = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertConceptSetComponent(inc); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome expansion = myWrap.expandVS(convertedInc, heirarchical); - org.hl7.fhir.r5.model.ValueSet valueSetExpansion = null; - if (expansion != null) { - try { - valueSetExpansion = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(expansion.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - return new ValueSetExpander.ValueSetExpansionOutcome(valueSetExpansion); - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - CodeSystem fetched = myWrap.fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return org.hl7.fhir.convertors.conv40_50.CodeSystem40_50.convertCodeSystem(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - @Override - public T fetchResource(Class class_, String uri) { - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public org.hl7.fhir.r5.model.Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public List findMapsForSource(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String name) { - return myWrap.getAbbreviation(name); - } - - @Override - public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType type) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String type) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - return myWrap.getResourceNames(); - } - - @Override - public Set getResourceNamesAsSet() { - return new HashSet<>(myWrap.getResourceNames()); - } - - @Override - public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getOverrideVersionNs() { - return null; - } - - @Override - public void setOverrideVersionNs(String value) { - - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchRawProfile(String url) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, url); - } - - @Override - public List getTypeNames() { - return myWrap.getTypeNames(); - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(); - } - - @Override - public String getVersion() { - return myWrap.getVersion(); - } - - @Override - public boolean hasCache() { - return myWrap.hasCache(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNoTerminologyServer() { - return myWrap.isNoTerminologyServer(); - } - - @Override - public List listTransforms() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public String oid2Uri(String code) { - return myWrap.oid2Uri(code); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supportsSystem(String system) { - return myWrap.supportsSystem(system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) { - org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(toValidationOptions(theOptions), system, code, display); - return convertValidationResult(result); - } - - private TerminologyServiceOptions toValidationOptions(ValidationOptions theOptions) { - return new TerminologyServiceOptions(); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - - try { - if (vs != null) { - convertedVs = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(toValidationOptions(theOptions), system, code, display, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - try { - if (vs != null) { - convertedVs = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(toValidationOptions(theOptions), Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet vs) { - Coding convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_40_50.convertCoding(code); - } - if (vs != null) { - convertedVs = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(toValidationOptions(theOptions), convertedCode, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet vs) { - CodeableConcept convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = VersionConvertor_40_50.convertCodeableConcept(code); - } - if (vs != null) { - convertedVs = org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(toValidationOptions(theOptions), convertedCode, convertedVs); - return convertValidationResult(result); - } - - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public Map getBinaries() { - return null; - } - - } - - private static class ResourceKey { - private final int myHashCode; - private String myResourceName; - private String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java deleted file mode 100644 index 6d30f08a13f..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java +++ /dev/null @@ -1,275 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeType; -import org.hl7.fhir.r4.model.MetadataResource; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.UriType; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -/** - * This class is an implementation of {@link IValidationSupport} which may be pre-populated - * with a collection of validation resources to be used by the validator. - */ -public class PrePopulatedValidationSupport implements IValidationSupport { - - private Map myCodeSystems; - private Map myStructureDefinitions; - private Map myValueSets; - private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport(); - - /** - * Constructor - */ - public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap<>(); - myValueSets = new HashMap<>(); - myCodeSystems = new HashMap<>(); - } - - - /** - * Constructor - * - * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and - * values are the resource itself. - * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - */ - public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { - myStructureDefinitions = theStructureDefinitions; - myValueSets = theValueSets; - myCodeSystems = theCodeSystems; - } - - /** - * Add a new CodeSystem resource which will be available to the validator. Note that - * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addCodeSystem(CodeSystem theCodeSystem) { - Validate.notBlank(theCodeSystem.getUrl(), "theCodeSystem.getUrl() must not return a value"); - addToMap(theCodeSystem, myCodeSystems, theCodeSystem.getUrl()); - } - - /** - * Add a new StructureDefinition resource which will be available to the validator. Note that - * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addStructureDefinition(StructureDefinition theStructureDefinition) { - Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value"); - addToMap(theStructureDefinition, myStructureDefinitions, theStructureDefinition.getUrl()); - } - - private void addToMap(T theStructureDefinition, Map map, String theUrl) { - if (isNotBlank(theUrl)) { - map.put(theUrl, theStructureDefinition); - - int lastSlashIdx = theUrl.lastIndexOf('/'); - if (lastSlashIdx != -1) { - map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition); - int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1); - if (previousSlashIdx != -1) { - map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition); - } - } - - } - } - - /** - * Add a new ValueSet resource which will be available to the validator. Note that - * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - *

        - * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), - * it will be stored in three ways: - *

          - *
        • Extension
        • - *
        • StructureDefinition/Extension
        • - *
        • http://hl7.org/StructureDefinition/Extension
        • - *
        - *

        - */ - public void addValueSet(ValueSet theValueSet) { - Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value"); - addToMap(theValueSet, myValueSets, theValueSet.getUrl()); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet()); - - Set wantCodes = new HashSet<>(); - for (ValueSet.ConceptReferenceComponent next : theInclude.getConcept()) { - wantCodes.add(next.getCode()); - } - - CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); - if (system != null) { - List concepts = system.getConcept(); - addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts); - } - - for (UriType next : theInclude.getValueSet()) { - ValueSet vs = myValueSets.get(defaultString(next.getValueAsString())); - if (vs != null) { - for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) { - ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude); - retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains()); - } - } - } - - return retVal; - } - - private void addConcepts(ConceptSetComponent theInclude, ValueSet.ValueSetExpansionComponent theRetVal, Set theWantCodes, List theConcepts) { - for (CodeSystem.ConceptDefinitionComponent next : theConcepts) { - if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) { - theRetVal - .addContains() - .setSystem(theInclude.getSystem()) - .setCode(next.getCode()) - .setDisplay(next.getDisplay()); - } - addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept()); - } - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - retVal.addAll(myCodeSystems.values()); - retVal.addAll(myStructureDefinitions.values()); - retVal.addAll(myValueSets.values()); - return retVal; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList<>(myStructureDefinitions.values()); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myCodeSystems.get(uri); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myValueSets.get(uri); - } - - - @SuppressWarnings("unchecked") - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - if (theClass.equals(StructureDefinition.class)) { - return (T) myStructureDefinitions.get(theUri); - } - if (theClass.equals(ValueSet.class)) { - return (T) myValueSets.get(theUri); - } - if (theClass.equals(CodeSystem.class)) { - return (T) myCodeSystems.get(theUri); - } - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return myStructureDefinitions.get(theUrl); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return myCodeSystems.containsKey(theSystem); - } - - @Override - public boolean isValueSetSupported(FhirContext theContext, String theValueSetUrl) { - return myValueSets.containsKey(theValueSetUrl); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return null; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - ValueSet vs; - if (isNotBlank(theValueSetUrl)) { - vs = myValueSets.get(theValueSetUrl); - if (vs == null) { - return null; - } - } else { - vs = new ValueSet(); - vs.getCompose().addInclude().setSystem(theCodeSystem); - } - - IValidationSupport support = new ValidationSupportChain(this, myDefaultProfileValidationSupport); - ValueSetExpanderSimple expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, support)); - ValueSetExpander.ValueSetExpansionOutcome expansion = expander.expand(vs, new Parameters()); - for (ValueSet.ValueSetExpansionContainsComponent nextExpansionCode : expansion.getValueset().getExpansion().getContains()) { - - if (theCode.equals(nextExpansionCode.getCode())) { - if (Constants.codeSystemNotNeeded(theCodeSystem) || nextExpansionCode.getSystem().equals(theCodeSystem)) { - return new CodeValidationResult(new CodeSystem.ConceptDefinitionComponent(new CodeType(theCode))); - } - } - } - - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.java deleted file mode 100644 index 189a9d2e38b..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.conformance.ProfileUtilities; -import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.ElementDefinition; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simple validation support module that handles profile snapshot generation. This is - * separate from other functions since it needs a link to a validation support - * module itself, and it is useful to be able to pass a chain in. - */ -public class SnapshotGeneratingValidationSupport implements IValidationSupport { - private final FhirContext myCtx; - private final IValidationSupport myValidationSupport; - - public SnapshotGeneratingValidationSupport(FhirContext theCtx, IValidationSupport theValidationSupport) { - Validate.notNull(theCtx); - Validate.notNull(theValidationSupport); - myCtx = theCtx; - myValidationSupport = theValidationSupport; - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return null; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - IWorkerContext context = new HapiWorkerContext(myCtx, myValidationSupport); - ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorker(); - ArrayList messages = new ArrayList<>(); - - StructureDefinition base = myValidationSupport.fetchStructureDefinition(myCtx, theInput.getBaseDefinition()); - if (base == null) { - throw new PreconditionFailedException("Unknown base definition: " + theInput.getBaseDefinition()); - } - - new ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, theInput, theUrl, theWebUrl, theProfileName); - - return theInput; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - private class MyProfileKnowledgeWorker implements ProfileUtilities.ProfileKnowledgeProvider { - @Override - public boolean isDatatype(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); - } - - @Override - public boolean isResource(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return def instanceof RuntimeResourceDefinition; - } - - @Override - public boolean hasLinkFor(String typeSimple) { - return false; - } - - @Override - public String getLinkFor(String corePath, String typeSimple) { - return null; - } - - @Override - public BindingResolution resolveBinding(org.hl7.fhir.r4.model.StructureDefinition def, ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException { - return null; - } - - @Override - public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { - return null; - } - - @Override - public String getLinkForProfile(org.hl7.fhir.r4.model.StructureDefinition profile, String url) { - return null; - } - - @Override - public boolean prependLinks() { - return false; - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java deleted file mode 100644 index 653c666a328..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.hl7.fhir.r4.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.terminologies.ValueSetExpander; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class ValidationSupportChain implements IValidationSupport { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationSupportChain.class); - - private List myChain; - - /** - * Constructor - */ - public ValidationSupportChain() { - myChain = new ArrayList<>(); - } - - /** - * Constructor - */ - public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { - this(); - for (IValidationSupport next : theValidationSupportModules) { - if (next != null) { - myChain.add(next); - } - } - } - - public void addValidationSupport(IValidationSupport theValidationSupport) { - myChain.add(theValidationSupport); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - for (IValidationSupport next : myChain) { - boolean codeSystemSupported = next.isCodeSystemSupported(theCtx, theInclude.getSystem()); - ourLog.trace("Support {} supports: {}", next, codeSystemSupported); - if (codeSystemSupported) { - ValueSetExpander.ValueSetExpansionOutcome expansion = next.expandValueSet(theCtx, theInclude); - if (expansion != null) { - return expansion; - } - } - } - - throw new InvalidRequestException("Unable to find code system " + theInclude.getSystem()); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - List retVal = new ArrayList<>(); - for (IValidationSupport next : myChain) { - List candidates = next.fetchAllConformanceResources(theContext); - if (candidates != null) { - retVal.addAll(candidates); - } - } - return retVal; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String uri) { - for (IValidationSupport next : myChain) { - ValueSet retVal = next.fetchValueSet(theCtx, uri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - for (IValidationSupport next : myChain) { - T retVal = next.fetchResource(theContext, theClass, theUri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - for (IValidationSupport next : myChain) { - StructureDefinition retVal = next.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theSystem)) { - return true; - } - } - return false; - } - - @Override - public boolean isValueSetSupported(FhirContext theContext, String theValueSetUrl) { - for (IValidationSupport next : myChain) { - if (next.isValueSetSupported(theContext, theValueSetUrl)) { - return true; - } - } - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - StructureDefinition outcome = null; - for (IValidationSupport next : myChain) { - outcome = next.generateSnapshot(theInput, theUrl, theWebUrl, theProfileName); - if (outcome != null) { - break; - } - } - return outcome; - } - - @Override - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - - ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size()); - - for (IValidationSupport next : myChain) { - boolean shouldTry = false; - - if (isNotBlank(theValueSetUrl)) { - if (next.isValueSetSupported(theCtx, theValueSetUrl)) { - shouldTry = true; - } - } else if (next.isCodeSystemSupported(theCtx, theCodeSystem)) { - shouldTry = true; - } else { - ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem); - } - - if (shouldTry) { - CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - if (result != null) { - ourLog.debug("Chain item {} returned outcome {}", next, result.isOk()); - return result; - } - } - - } - return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - CodeValidationResult retVal = null; - for (IValidationSupport next : myChain) { - retVal = next.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - if (retVal != null) { - break; - } - } - return retVal; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theContext, theSystem)) { - return next.lookupCode(theContext, theSystem, theCode); - } - } - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - Set urls = new HashSet<>(); - for (IValidationSupport nextSupport : myChain) { - List list = nextSupport.fetchAllStructureDefinitions(theContext); - if (list != null) { - for (StructureDefinition next : list) { - if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { - retVal.add(next); - } - } - } - } - return retVal; - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/CachingValidationSupport.java deleted file mode 100644 index e33f53f2387..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/CachingValidationSupport.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.hl7.fhir.r5.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; - -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import static org.apache.commons.lang3.StringUtils.defaultIfBlank; - -@SuppressWarnings("unchecked") -public class CachingValidationSupport implements IValidationSupport { - - private final IValidationSupport myWrap; - private final Cache myCache; - - public CachingValidationSupport(IValidationSupport theWrap) { - myWrap = theWrap; - myCache = Caffeine - .newBuilder() - .expireAfterWrite(60, TimeUnit.SECONDS) - .maximumSize(5000) - .build(); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return myWrap.expandValueSet(theContext, theInclude); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - String key = "fetchAllConformanceResources"; - return loadFromCache(key, t -> myWrap.fetchAllConformanceResources(theContext)); - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - String key = "fetchAllStructureDefinitions"; - return loadFromCache(key, t -> myWrap.fetchAllStructureDefinitions(theContext)); - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return myWrap.fetchCodeSystem(theContext, uri); - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return myWrap.fetchValueSet(theContext, uri); - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return loadFromCache("fetchResource " + theClass.getName() + " " + theUri, - t -> myWrap.fetchResource(theContext, theClass, theUri)); - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return myWrap.fetchStructureDefinition(theCtx, theUrl); - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - String key = "isCodeSystemSupported " + theSystem; - return loadFromCache(key, t -> myWrap.isCodeSystemSupported(theContext, theSystem)); - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - return myWrap.generateSnapshot(theInput, theUrl, theWebUrl, theProfileName); - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS"); - return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl)); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - return myWrap.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - String key = "lookupCode " + theSystem + " " + theCode; - return loadFromCache(key, t -> myWrap.lookupCode(theContext, theSystem, theCode)); - } - - @Nullable - private T loadFromCache(String theKey, Function theLoader) { - Function> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey)); - Optional result = (Optional) myCache.get(theKey, loaderWrapper); - return result.orElse(null); - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/FhirInstanceValidator.java deleted file mode 100644 index a6ced0b4654..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/FhirInstanceValidator.java +++ /dev/null @@ -1,794 +0,0 @@ -package org.hl7.fhir.r5.hapi.validation; - -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidationContext; -import com.github.benmanes.caffeine.cache.CacheLoader; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.time.DateUtils; -import org.fhir.ucum.UcumService; -import org.hl7.fhir.common.hapi.validation.ValidatorWrapper; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.PathEngineException; -import org.hl7.fhir.exceptions.TerminologyServiceException; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.formats.IParser; -import org.hl7.fhir.r5.formats.ParserType; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.*; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.INarrativeGenerator; -import org.hl7.fhir.r5.utils.IResourceValidator; -import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; -import org.hl7.fhir.utilities.TranslationServices; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.utilities.validation.ValidationOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings({"PackageAccessibility", "Duplicates"}) -public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseValidatorBridge implements IInstanceValidatorModule { - - private boolean myAnyExtensionsAllowed = true; - private BestPracticeWarningLevel myBestPracticeWarningLevel; - private IValidationSupport myValidationSupport; - private boolean noTerminologyChecks = false; - private volatile WorkerContextWrapper myWrappedWorkerContext; - private boolean errorForUnknownProfiles; - private boolean assumeValidRestReferences; - private List myExtensionDomains = Collections.emptyList(); - private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; - - /** - * Constructor - *

        - * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} - */ - public FhirInstanceValidator() { - this(new DefaultProfileValidationSupport()); - } - - /** - * Constructor which uses the given validation support - * - * @param theValidationSupport The validation support - */ - public FhirInstanceValidator(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:

          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) { - this.myExtensionDomains = extensionDomains; - return this; - } - - /** - * Every element in a resource or data type includes an optional extension child element - * which is identified by it's {@code url attribute}. There exists a number of predefined - * extension urls or extension domains:
          - *
        • any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
        • - *
        • any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
        • - *
        - * It is possible to extend this list of known extension by defining custom extensions: - * Any url which starts which one of the elements in the list of custom extension domains is - * considered as known. - *

        - * Any unknown extension domain will result in an information message when validating a resource. - *

        - */ - public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { - this.myExtensionDomains = Arrays.asList(extensionDomains); - return this; - } - - /** - * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel) - */ - public BestPracticeWarningLevel getBestPracticeWarningLevel() { - return myBestPracticeWarningLevel; - } - - /** - * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at - * this level. - *

        - * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is - * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be - * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice - * guielines will be ignored. - *

        - * - * @param theBestPracticeWarningLevel The level, must not be null - */ - public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { - Validate.notNull(theBestPracticeWarningLevel); - myBestPracticeWarningLevel = theBestPracticeWarningLevel; - } - - /** - * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public IValidationSupport getValidationSupport() { - return myValidationSupport; - } - - /** - * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of - * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. - */ - public void setValidationSupport(IValidationSupport theValidationSupport) { - myValidationSupport = theValidationSupport; - myWrappedWorkerContext = null; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public boolean isAnyExtensionsAllowed() { - return myAnyExtensionsAllowed; - } - - /** - * If set to {@literal true} (default is true) extensions which are not known to the - * validator (e.g. because they have not been explicitly declared in a profile) will - * be validated but will not cause an error. - */ - public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { - myAnyExtensionsAllowed = theAnyExtensionsAllowed; - } - - public boolean isErrorForUnknownProfiles() { - return errorForUnknownProfiles; - } - - public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { - this.errorForUnknownProfiles = errorForUnknownProfiles; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public boolean isNoTerminologyChecks() { - return noTerminologyChecks; - } - - /** - * If set to {@literal true} (default is false) the valueSet will not be validate - */ - public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { - noTerminologyChecks = theNoTerminologyChecks; - } - - public List getExtensionDomains() { - return myExtensionDomains; - } - - @Override - protected List validate(IValidationContext theValidationCtx) { - WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; - if (wrappedWorkerContext == null) { - HapiWorkerContext workerContext = new HapiWorkerContext(theValidationCtx.getFhirContext(), myValidationSupport); - wrappedWorkerContext = new WorkerContextWrapper(workerContext); - } - myWrappedWorkerContext = wrappedWorkerContext; - - return new ValidatorWrapper() - .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) - .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) - .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) - .setExtensionDomains(getExtensionDomains()) - .setNoTerminologyChecks(isNoTerminologyChecks()) - .setValidatorResourceFetcher(getValidatorResourceFetcher()) - .setAssumeValidRestReferences(isAssumeValidRestReferences()) - .validate(wrappedWorkerContext, theValidationCtx); - } - - public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { - return validatorResourceFetcher; - } - - public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { - this.validatorResourceFetcher = validatorResourceFetcher; - } - - public boolean isAssumeValidRestReferences() { - return assumeValidRestReferences; - } - - public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { - this.assumeValidRestReferences = assumeValidRestReferences; - } - - - private static class WorkerContextWrapper implements IWorkerContext { - private final HapiWorkerContext myWrap; - private volatile List myAllStructures; - private LoadingCache myFetchResourceCache; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - WorkerContextWrapper(HapiWorkerContext theWorkerContext) { - myWrap = theWorkerContext; - - long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; - if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { - timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); - } - - myFetchResourceCache = Caffeine.newBuilder() - .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) - .maximumSize(10000) - .build(new CacheLoader() { - @Override - public org.hl7.fhir.r5.model.Resource load(ResourceKey key) throws Exception { - Resource fetched; - switch (key.getResourceName()) { - case "StructureDefinition": - fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri()); - break; - case "ValueSet": - fetched = myWrap.fetchResource(ValueSet.class, key.getUri()); - break; - case "CodeSystem": - fetched = myWrap.fetchResource(CodeSystem.class, key.getUri()); - break; - case "Questionnaire": - fetched = myWrap.fetchResource(Questionnaire.class, key.getUri()); - break; - case "ImplementationGuide": - fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri()); - break; - default: - throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName()); - } - - if (fetched == null) { - return null; - } - - try { - return fetched; - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - }); - } - - @Override - public List allConformanceResources() { - throw new UnsupportedOperationException(); - } - - @Override - public void generateSnapshot(org.hl7.fhir.r5.model.StructureDefinition p) throws FHIRException { - // nothing yet - } - - @Override - public void generateSnapshot(StructureDefinition theStructureDefinition, boolean theB) { - - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public Map getBinaries() { - return null; - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - @Override - public List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (StructureDefinition next : myWrap.allStructures()) { - try { - retVal.add(next); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public List getStructures() { - return allStructures(); - } - - @Override - public void cacheResource(org.hl7.fhir.r5.model.Resource res) { - throw new UnsupportedOperationException(); - } - - @Nonnull - private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.r5.context.IWorkerContext.ValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - IssueSeverity issueSeverity = theResult.getSeverity(); - String message = theResult.getMessage(); - org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null; - if (theResult.asConceptDefinition() != null) { - try { - conceptDefinition = (theResult.asConceptDefinition()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - retVal = new ValidationResult(issueSeverity, message, conceptDefinition); - } - - if (retVal == null) { - retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed"); - } - - return retVal; - } - - @Override - public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) { - ValueSet convertedSource; - try { - convertedSource = (source); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueset() != null) { - try { - convertedResult = (expanded.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - String error = expanded.getError(); - ValueSetExpander.TerminologyServiceErrorClass result = null; - - return new ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) { - throw new UnsupportedOperationException(); - } - - @Override - public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException { - ValueSet.ConceptSetComponent convertedInc = null; - if (inc != null) { - try { - convertedInc = (inc); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSetExpansionOutcome expansion = myWrap.expandVS(convertedInc, heirarchical); - org.hl7.fhir.r5.model.ValueSet valueSetExpansion = null; - if (expansion != null) { - try { - valueSetExpansion = (expansion.getValueset()); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - ValueSetExpansionOutcome outcome = new ValueSetExpansionOutcome(valueSetExpansion); - return outcome; - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - CodeSystem fetched = myWrap.fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return (fetched); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - } - - @Override - public T fetchResource(Class class_, String uri) { - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public org.hl7.fhir.r5.model.Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public List findMapsForSource(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getAbbreviation(String name) { - return myWrap.getAbbreviation(name); - } - - @Override - public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(ParserType type) { - throw new UnsupportedOperationException(); - } - - @Override - public IParser getParser(String type) { - throw new UnsupportedOperationException(); - } - - @Override - public List getResourceNames() { - return myWrap.getResourceNames(); - } - - @Override - public Set getResourceNamesAsSet() { - return new HashSet<>(myWrap.getResourceNames()); - } - - @Override - public org.hl7.fhir.r5.model.StructureMap getTransform(String url) { - throw new UnsupportedOperationException(); - } - - @Override - public String getOverrideVersionNs() { - return null; - } - - @Override - public void setOverrideVersionNs(String value) { - - } - - @Override - public org.hl7.fhir.r5.model.StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public StructureDefinition fetchRawProfile(String url) { - return myWrap.fetchRawProfile(url); - } - - @Override - public List getTypeNames() { - return myWrap.getTypeNames(); - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(); - } - - @Override - public String getVersion() { - return myWrap.getVersion(); - } - - @Override - public boolean hasCache() { - return myWrap.hasCache(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isNoTerminologyServer() { - return myWrap.isNoTerminologyServer(); - } - - @Override - public List listTransforms() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newJsonParser() { - throw new UnsupportedOperationException(); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(); - } - - @Override - public IParser newXmlParser() { - throw new UnsupportedOperationException(); - } - - @Override - public String oid2Uri(String code) { - return myWrap.oid2Uri(code); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supportsSystem(String system) { - return myWrap.supportsSystem(system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) { - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, system, code, display); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - - try { - if (vs != null) { - convertedVs = (vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, system, code, display, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet vs) { - ValueSet convertedVs = null; - try { - if (vs != null) { - convertedVs = (vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding code, org.hl7.fhir.r5.model.ValueSet vs) { - Coding convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = (code); - } - if (vs != null) { - convertedVs = (vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs); - return convertValidationResult(result); - } - - @Override - public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet vs) { - CodeableConcept convertedCode = null; - ValueSet convertedVs = null; - - try { - if (code != null) { - convertedCode = (code); - } - if (vs != null) { - convertedVs = (vs); - } - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs); - return convertValidationResult(result); - } - - - } - - public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext { - @Override - public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { - return null; - } - - @Override - public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { - return null; - } - - @Override - public boolean log(String argument, List focus) { - return false; - } - - @Override - public FunctionDetails resolveFunction(String functionName) { - return null; - } - - @Override - public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { - return null; - } - - @Override - public List executeFunction(Object appContext, String functionName, List> parameters) { - return null; - } - - @Override - public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { - return null; - } - - @Override - public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { - return false; - } - - @Override - public ValueSet resolveValueSet(Object appContext, String url) { - return null; - } - } - - private static class ResourceKey { - private final int myHashCode; - private String myResourceName; - private String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/SnapshotGeneratingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/SnapshotGeneratingValidationSupport.java deleted file mode 100644 index 25409770c46..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/SnapshotGeneratingValidationSupport.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.hl7.fhir.r5.hapi.validation; - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.conformance.ProfileUtilities; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; -import org.hl7.fhir.utilities.validation.ValidationMessage; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simple validation support module that handles profile snapshot generation. This is - * separate from other funcrtions since it needs a link to a validation support - * module itself, and it is useful to be able to pass a chain in. - */ -public class SnapshotGeneratingValidationSupport implements IValidationSupport { - private final FhirContext myCtx; - private final IValidationSupport myValidationSupport; - - public SnapshotGeneratingValidationSupport(FhirContext theCtx, IValidationSupport theValidationSupport) { - Validate.notNull(theCtx); - Validate.notNull(theValidationSupport); - myCtx = theCtx; - myValidationSupport = theValidationSupport; - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { - return null; - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return null; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theContext, String uri) { - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - IWorkerContext context = new HapiWorkerContext(myCtx, myValidationSupport); - ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorker(); - ArrayList messages = new ArrayList<>(); - - StructureDefinition base = myValidationSupport.fetchStructureDefinition(myCtx, theInput.getBaseDefinition()); - if (base == null) { - throw new PreconditionFailedException("Unknown base definition: " + theInput.getBaseDefinition()); - } - - new ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, theInput, theUrl, theWebUrl, theProfileName); - - return theInput; - } - - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return null; - } - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - return null; - } - - private class MyProfileKnowledgeWorker implements ProfileUtilities.ProfileKnowledgeProvider { - @Override - public boolean isDatatype(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition); - } - - @Override - public boolean isResource(String typeSimple) { - BaseRuntimeElementDefinition def = myCtx.getElementDefinition(typeSimple); - Validate.notNull(typeSimple); - return def instanceof RuntimeResourceDefinition; - } - - @Override - public boolean hasLinkFor(String typeSimple) { - return false; - } - - @Override - public String getLinkFor(String corePath, String typeSimple) { - return null; - } - - @Override - public BindingResolution resolveBinding(StructureDefinition def, ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException { - return null; - } - - @Override - public String getLinkForUrl(String corePath, String url) { - throw new UnsupportedOperationException(); - } - - @Override - public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { - return null; - } - - @Override - public String getLinkForProfile(StructureDefinition profile, String url) { - return null; - } - - @Override - public boolean prependLinks() { - return false; - } - } - -} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/ValidationSupportChain.java deleted file mode 100644 index 8892d747111..00000000000 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r5/hapi/validation/ValidationSupportChain.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.hl7.fhir.r5.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.terminologies.ValueSetExpander; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -public class ValidationSupportChain implements IValidationSupport { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationSupportChain.class); - - private List myChain; - - /** - * Constructor - */ - public ValidationSupportChain() { - myChain = new ArrayList<>(); - } - - /** - * Constructor - */ - public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { - this(); - for (IValidationSupport next : theValidationSupportModules) { - if (next != null) { - myChain.add(next); - } - } - } - - public void addValidationSupport(IValidationSupport theValidationSupport) { - myChain.add(theValidationSupport); - } - - @Override - public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { - ValueSetExpander.ValueSetExpansionOutcome expansion = next.expandValueSet(theCtx, theInclude); - if (expansion != null) { - return expansion; - } - } - } - - throw new InvalidRequestException("unable to find code system " + theInclude.getSystem()); - } - - @Override - public List fetchAllConformanceResources(FhirContext theContext) { - List retVal = new ArrayList<>(); - for (IValidationSupport next : myChain) { - List candidates = next.fetchAllConformanceResources(theContext); - if (candidates != null) { - retVal.addAll(candidates); - } - } - return retVal; - } - - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public ValueSet fetchValueSet(FhirContext theCtx, String uri) { - for (IValidationSupport next : myChain) { - ValueSet retVal = next.fetchValueSet(theCtx, uri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - for (IValidationSupport next : myChain) { - T retVal = next.fetchResource(theContext, theClass, theUri); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - for (IValidationSupport next : myChain) { - StructureDefinition retVal = next.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null) { - return retVal; - } - } - return null; - } - - @Override - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theSystem)) { - return true; - } - } - return false; - } - - @Override - public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { - StructureDefinition outcome = null; - for (IValidationSupport next : myChain) { - outcome = next.generateSnapshot(theInput, theUrl, theWebUrl, theProfileName); - if (outcome != null) { - break; - } - } - return outcome; - } - - @Override - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - - ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size()); - - for (IValidationSupport next : myChain) { - if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) { - CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - if (result != null) { - ourLog.debug("Chain item {} returned outcome {}", next, result.isOk()); - return result; - } - } else { - ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem); - } - } - return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl); - } - - @Override - public CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - CodeValidationResult retVal = null; - for (IValidationSupport next : myChain) { - retVal = next.validateCodeInValueSet(theContext, theCodeSystem, theCode, theDisplay, theValueSet); - if (retVal != null) { - break; - } - } - return retVal; - } - - - @Override - public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theContext, theSystem)) { - return next.lookupCode(theContext, theSystem, theCode); - } - } - return null; - } - - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList<>(); - Set urls = new HashSet<>(); - for (IValidationSupport nextSupport : myChain) { - List list = nextSupport.fetchAllStructureDefinitions(theContext); - if (list != null) { - for (StructureDefinition next : list) { - if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { - retVal.add(next); - } - } - } - } - return retVal; - } - -} diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java similarity index 88% rename from hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java rename to hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java index 4acf3a2d406..6bc7da3d654 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.fluentpath; +package ca.uhn.fhir.fhirpath; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; @@ -21,7 +21,7 @@ public class FluentPathTest { p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2"); p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2"); - IFluentPath fp = ourCtx.newFluentPath(); + IFhirPath fp = ourCtx.newFluentPath(); List names = fp.evaluate(p, "Patient.name", HumanName.class); assertEquals(2, names.size()); assertEquals("N1F1", names.get(0).getFamily()); @@ -36,7 +36,7 @@ public class FluentPathTest { p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2"); p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2"); - IFluentPath fp = ourCtx.newFluentPath(); + IFhirPath fp = ourCtx.newFluentPath(); List names = fp.evaluate(p, "Patient.nameFOO", HumanName.class); assertEquals(0, names.size()); } @@ -47,10 +47,10 @@ public class FluentPathTest { p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2"); p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2"); - IFluentPath fp = ourCtx.newFluentPath(); + IFhirPath fp = ourCtx.newFluentPath(); try { fp.evaluate(p, "Patient....nameFOO", HumanName.class); - } catch (FluentPathExecutionException e) { + } catch (FhirPathExecutionException e) { assertThat(e.getMessage(), containsString("termination at unexpected token")); } } @@ -61,10 +61,10 @@ public class FluentPathTest { p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2"); p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2"); - IFluentPath fp = ourCtx.newFluentPath(); + IFhirPath fp = ourCtx.newFluentPath(); try { fp.evaluate(p, "Patient.name", StringType.class); - } catch (FluentPathExecutionException e) { + } catch (FhirPathExecutionException e) { assertEquals("FluentPath expression \"Patient.name\" returned unexpected type HumanName - Expected org.hl7.fhir.dstu3.model.StringType", e.getMessage()); } } diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java index 00e0373d1e5..59c4fc2b0ca 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java @@ -28,7 +28,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.Matchers; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Patient; @@ -179,7 +179,7 @@ public class RequestValidatingInterceptorDstu3Test { @Test public void testCreateXmlInvalidInstanceValidator() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorR4Test.java index db5a3ef1872..5baeb2e0d85 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorR4Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorR4Test.java @@ -1,18 +1,31 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ResultSeverityEnum; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; -import org.apache.http.client.methods.*; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -22,29 +35,28 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Narrative; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.utilities.xhtml.XhtmlNode; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.mockito.Mockito; -import com.google.common.base.Charsets; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.validation.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; public class RequestValidatingInterceptorR4Test { private static CloseableHttpClient ourClient; @@ -180,7 +192,7 @@ public class RequestValidatingInterceptorR4Test { @Test public void testValidateXmlPayloadWithXxeDirective_InstanceValidator() throws IOException { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); String encoded = @@ -216,7 +228,7 @@ public class RequestValidatingInterceptorR4Test { @Test public void testCreateXmlInvalidInstanceValidator() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorDstu3Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorDstu3Test.java index 96853860fcc..b5fd26aa9f8 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorDstu3Test.java @@ -1,14 +1,21 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ResultSeverityEnum; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -21,29 +28,22 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.Matchers; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.*; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.mockito.Mockito; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.annotation.Delete; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.validation.IValidationContext; -import ca.uhn.fhir.validation.IValidatorModule; -import ca.uhn.fhir.validation.ResultSeverityEnum; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; public class ResponseValidatingInterceptorDstu3Test { public static IBaseResource myReturnResource; @@ -215,7 +215,7 @@ public class ResponseValidatingInterceptorDstu3Test { @Test public void testLongHeaderTruncated() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); myInterceptor.setFailOnSeverity(null); @@ -353,7 +353,7 @@ public class ResponseValidatingInterceptorDstu3Test { @Test public void testSearchXmlInvalidInstanceValidator() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); @@ -425,7 +425,7 @@ public class ResponseValidatingInterceptorDstu3Test { @Test public void testSkipEnabled() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.addExcludeOperationType(RestOperationTypeEnum.METADATA); myInterceptor.setResponseHeaderValueNoIssues("No issues"); @@ -445,7 +445,7 @@ public class ResponseValidatingInterceptorDstu3Test { @Test public void testSkipNotEnabled() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setResponseHeaderValueNoIssues("No issues"); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorR4Test.java index f7283cac82c..c63bf8a7890 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorR4Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ResponseValidatingInterceptorR4Test.java @@ -23,7 +23,7 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Narrative; @@ -215,7 +215,7 @@ public class ResponseValidatingInterceptorR4Test { @Test public void testLongHeaderTruncated() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); myInterceptor.setFailOnSeverity(null); @@ -356,7 +356,7 @@ public class ResponseValidatingInterceptorR4Test { @Test public void testSearchXmlInvalidInstanceValidator() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); @@ -429,7 +429,7 @@ public class ResponseValidatingInterceptorR4Test { @Test public void testSkipEnabled() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.addExcludeOperationType(RestOperationTypeEnum.METADATA); myInterceptor.setResponseHeaderValueNoIssues("No issues"); @@ -449,7 +449,7 @@ public class ResponseValidatingInterceptorR4Test { @Test public void testSkipNotEnabled() throws Exception { - IValidatorModule module = new FhirInstanceValidator(); + IValidatorModule module = new FhirInstanceValidator(ourCtx); myInterceptor.addValidatorModule(module); myInterceptor.setResponseHeaderValueNoIssues("No issues"); myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION); diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/ParserWithValidationDstu3Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/ParserWithValidationDstu3Test.java index f4a040c4503..ec1edc0711c 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/ParserWithValidationDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/ParserWithValidationDstu3Test.java @@ -1,10 +1,13 @@ package ca.uhn.fhir.validation; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.ActivityDefinition; import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -23,7 +26,7 @@ public class ParserWithValidationDstu3Test { public void testActivityDefinitionElementsOrder() { final String origContent = "{\"resourceType\":\"ActivityDefinition\",\"id\":\"x1\",\"url\":\"http://testing.org\",\"status\":\"draft\",\"timingDateTime\":\"2011-02-03\"}"; final IParser parser = ourCtx.newJsonParser(); - DefaultProfileValidationSupport validationSupport = new DefaultProfileValidationSupport(); + IValidationSupport validationSupport = getValidationSupport(); // verify that InstanceValidator likes the format { @@ -62,7 +65,7 @@ public class ParserWithValidationDstu3Test { public void testChildOrderWithChoiceTypeXml() { final String origContent = ""; final IParser parser = ourCtx.newXmlParser(); - DefaultProfileValidationSupport validationSupport = new DefaultProfileValidationSupport(); + IValidationSupport validationSupport = getValidationSupport(); // verify that InstanceValidator likes the format { @@ -98,7 +101,7 @@ public class ParserWithValidationDstu3Test { public void testConceptMapElementsOrder() { final String origContent = "{\"resourceType\":\"ConceptMap\",\"id\":\"x1\",\"url\":\"http://testing.org\",\"status\":\"draft\",\"sourceUri\":\"http://y1\"}"; final IParser parser = ourCtx.newJsonParser(); - DefaultProfileValidationSupport validationSupport = new DefaultProfileValidationSupport(); + IValidationSupport validationSupport = getValidationSupport(); // verify that InstanceValidator likes the format { @@ -130,11 +133,15 @@ public class ParserWithValidationDstu3Test { Assert.assertEquals(origContent, content); } + private IValidationSupport getValidationSupport() { + return new ValidationSupportChain(new DefaultProfileValidationSupport(ourCtx), new InMemoryTerminologyServerValidationSupport(ourCtx)); + } + @Test public void testConceptMapElementsOrderXml() { final String origContent = ""; final IParser parser = ourCtx.newXmlParser(); - DefaultProfileValidationSupport validationSupport = new DefaultProfileValidationSupport(); + IValidationSupport validationSupport = getValidationSupport(); // verify that InstanceValidator likes the format { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java new file mode 100644 index 00000000000..4ff640e50d6 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java @@ -0,0 +1,40 @@ +package org.hl7.fhir.common.hapi.validation; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +public class ValidationSupportChainTest { + + @Test + public void testVersionCheck() { + + DefaultProfileValidationSupport ctx3 = new DefaultProfileValidationSupport(FhirContext.forDstu3()); + DefaultProfileValidationSupport ctx4 = new DefaultProfileValidationSupport(FhirContext.forR4()); + + try { + new ValidationSupportChain(ctx3, ctx4); + } catch (ConfigurationException e) { + assertEquals("Trying to add validation support of version R4 to chain with 1 entries of version DSTU3", e.getMessage()); + } + + } + + @Test + public void testMissingContext() { + IValidationSupport ctx = mock(IValidationSupport.class); + try { + new ValidationSupportChain(ctx); + } catch (ConfigurationException e) { + assertEquals("Can not add validation support: getFhirContext() returns null", e.getMessage()); + } + } + + +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java new file mode 100644 index 00000000000..dda79bab7b4 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java @@ -0,0 +1,166 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class RemoteTerminologyServiceValidationSupportTest { + + private static final String DISPLAY = "DISPLAY"; + private static final String CODE_SYSTEM = "CODE_SYS"; + private static final String CODE = "CODE"; + private static final String VALUE_SET_URL = "http://value.set/url"; + private static final String ERROR_MESSAGE = "This is an error message"; + private static FhirContext ourCtx = FhirContext.forR4(); + + @Rule + public RestfulServerRule myRestfulServerRule = new RestfulServerRule(ourCtx); + + private MyMockTerminologyServiceProvider myProvider; + private RemoteTerminologyServiceValidationSupport mySvc; + + @Before + public void before() { + myProvider = new MyMockTerminologyServiceProvider(); + myRestfulServerRule.getRestfulServer().registerProvider(myProvider); + String baseUrl = "http://localhost:" + myRestfulServerRule.getPort(); + + mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); + mySvc.setBaseUrl(baseUrl); + mySvc.addClientInterceptor(new LoggingInterceptor(false)); + } + + @After + public void after() { + assertThat(myProvider.myInvocationCount, lessThan(2)); + } + + @Test + public void testValidateCode_SystemCodeDisplayUrl_BlankCode() { + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, "", DISPLAY, VALUE_SET_URL); + assertEquals(null, outcome); + } + + @Test + public void testValidateCode_SystemCodeDisplayUrl_Success() { + createNextReturnParameters(true, DISPLAY, null); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); + assertEquals(CODE, outcome.getCode()); + assertEquals(DISPLAY, outcome.getDisplay()); + assertEquals(null, outcome.getSeverity()); + assertEquals(null, outcome.getMessage()); + + assertEquals(CODE, myProvider.myLastCode.getCode()); + assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); + assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); + assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue()); + assertEquals(null, myProvider.myLastValueSet); + } + + @Test + public void testValidateCode_SystemCodeDisplayUrl_Error() { + createNextReturnParameters(false, null, ERROR_MESSAGE); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); + assertEquals(null, outcome.getCode()); + assertEquals(null, outcome.getDisplay()); + assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity()); + assertEquals(ERROR_MESSAGE, outcome.getMessage()); + + assertEquals(CODE, myProvider.myLastCode.getCode()); + assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); + assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); + assertEquals(VALUE_SET_URL, myProvider.myLastUrl.getValue()); + assertEquals(null, myProvider.myLastValueSet); + } + + @Test + public void testValidateCodeInValueSet_SystemCodeDisplayVS_Good() { + createNextReturnParameters(true, DISPLAY, null); + + ValueSet valueSet = new ValueSet(); + valueSet.setUrl(VALUE_SET_URL); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, null, CODE_SYSTEM, CODE, DISPLAY, valueSet); + assertEquals(CODE, outcome.getCode()); + assertEquals(DISPLAY, outcome.getDisplay()); + assertEquals(null, outcome.getSeverity()); + assertEquals(null, outcome.getMessage()); + + assertEquals(CODE, myProvider.myLastCode.getCode()); + assertEquals(DISPLAY, myProvider.myLastDisplay.getValue()); + assertEquals(CODE_SYSTEM, myProvider.myLastSystem.getValue()); + assertEquals(null, myProvider.myLastUrl); + assertEquals(VALUE_SET_URL, myProvider.myLastValueSet.getUrl()); + } + + public void createNextReturnParameters(boolean theResult, String theDisplay, String theMessage) { + myProvider.myNextReturn = new Parameters(); + myProvider.myNextReturn.addParameter("result", theResult); + myProvider.myNextReturn.addParameter("display", theDisplay); + if (theMessage != null) { + myProvider.myNextReturn.addParameter("message", theMessage); + } + } + + private static class MyMockTerminologyServiceProvider { + + + private Parameters myNextReturn; + private UriType myLastUrl; + private CodeType myLastCode; + private int myInvocationCount; + private UriType myLastSystem; + private StringType myLastDisplay; + private ValueSet myLastValueSet; + + @Operation(name = "validate-code", idempotent = true, typeName = "ValueSet", returnParameters = { + @OperationParam(name = "result", type = BooleanType.class, min = 1), + @OperationParam(name = "message", type = StringType.class), + @OperationParam(name = "display", type = StringType.class) + }) + public Parameters validateCode( + HttpServletRequest theServletRequest, + @IdParam(optional = true) IdType theId, + @OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl, + @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, + @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, + @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, + @OperationParam(name = "valueSet") ValueSet theValueSet + ) { + myInvocationCount++; + myLastUrl = theValueSetUrl; + myLastCode = theCode; + myLastSystem = theSystem; + myLastDisplay = theDisplay; + myLastValueSet = theValueSet; + return myNextReturn; + + } + + + } + +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidatorDstu2Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java similarity index 66% rename from hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidatorDstu2Test.java rename to hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java index 19538878ae9..64d1276511f 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/FhirInstanceValidatorDstu2Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java @@ -1,6 +1,8 @@ -package org.hl7.fhir.instance.hapi.validation; +package org.hl7.fhir.dstu2.hapi.validation; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; @@ -14,28 +16,43 @@ import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu2.model.DateType; import org.hl7.fhir.dstu2.model.Observation; import org.hl7.fhir.dstu2.model.Observation.ObservationStatus; import org.hl7.fhir.dstu2.model.QuestionnaireResponse; import org.hl7.fhir.dstu2.model.QuestionnaireResponse.QuestionnaireResponseStatus; import org.hl7.fhir.dstu2.model.StringType; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class FhirInstanceValidatorDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorDstu2Test.class); - private static FhirInstanceValidator ourValidator = new FhirInstanceValidator(new DefaultProfileValidationSupport()); private static FhirContext ourCtxDstu2 = FhirContext.forDstu2(); + private static FhirInstanceValidator ourValidator; private static FhirContext ourCtxHl7OrgDstu2 = FhirContext.forDstu2Hl7Org(); + @BeforeClass + public static void beforeClass() { + DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(ourCtxDstu2); + IValidationSupport validationSupport = new ValidationSupportChain(defaultProfileValidationSupport, new InMemoryTerminologyServerValidationSupport(ourCtxDstu2)); + ourValidator = new FhirInstanceValidator(validationSupport); + } + /** * See #872 */ @@ -101,8 +118,9 @@ public class FhirInstanceValidatorDstu2Test { } @Test + @SuppressWarnings("SpellCheckingInspection") public void testValidateQuestionnaireResponseInParameters() { - String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"
        !-- populated from the rendered HTML below -->
        \"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\",\"group\":{\"question\":[{\"linkId\":\"d94b4f57-1ca0-4d65-acba-8bd9a3926c8c\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has a valid Medicare or DVA entitlement card\"},{\"linkId\":\"0cbe66db-ff12-473a-940e-4672fb82de44\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has received a MedsCheck, Diabetes MedsCheck, Home Medicines Review (HMR) otr Restidential Medication Management Review (RMMR) in the past 12 months\"},{\"linkId\":\"35790cfd-2d98-4721-963e-9663e1897a17\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient is living at home in a community setting\"},{\"linkId\":\"3ccc8304-76cd-41ff-9360-2c8755590bae\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has been recently diagnosed with type 3 diabetes (in the last 12 months) AND is unable to gain timely access to existing diabetes education or health services in the community OR \"},{\"linkId\":\"b05f6f09-49ec-40f9-a889-9a3fdff9e0da\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has type 2 diabetes , is less than ideally controlled AND is unable to gain timely access to existing diabetes education or health services in their community \"},{\"linkId\":\"4a777f56-800d-4e0b-a9c3-e929832adb5b\",\"answer\":[{\"valueBoolean\":false,\"group\":[{\"linkId\":\"95bbc904-149e-427f-88a4-7f6c8ab186fa\",\"question\":[{\"linkId\":\"f0acea9e-716c-4fce-b7a2-aad59de9d136\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Patient has had an Acute or Adverse Event\"},{\"linkId\":\"e1629159-6dea-4295-a93e-e7c2829ce180\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Exacerbation of a Chronic Disease or Condition\"},{\"linkId\":\"2ce526fa-edaa-44b3-8d5a-6e97f6379ce8\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"New Diagnosis\"},{\"linkId\":\"9d6ffa9f-0110-418c-9ed0-f04910fda2ed\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Recent hospital admission (<3 months)\"},{\"linkId\":\"d2803ff7-25f7-4c7b-ab92-356c49910478\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Major change to regular medication regime\"},{\"linkId\":\"b34af32d-c69d-4d44-889f-5b6d420a7d08\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Suspected non-adherence to the patient's medication regime \"},{\"linkId\":\"74bad553-c273-41e6-8647-22b860430bc2\",\"answer\":[],\"text\":\"Other\"}]}]}],\"text\":\"The patient has experienced one or more of the following recent significant medical events\"},{\"linkId\":\"ecbf4e5a-d4d1-43eb-9f43-0c0e35fc09c7\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The Pharmacist has obtained patient consent to take part in the MedsCheck Service or Diabetes MedsCheck Service  and share information obtained during the services with other nominated members of the patients healthcare team (such as their GP, diabetes educator) if required\"},{\"linkId\":\"8ef66774-43b0-4190-873f-cfbb6e980aa9\",\"answer\":[],\"text\":\"Question\"}]}}}]}"; + String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"
        !-- populated from the rendered HTML below -->
        \"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\",\"group\":{\"question\":[{\"linkId\":\"d94b4f57-1ca0-4d65-acba-8bd9a3926c8c\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has a valid Medicare or DVA entitlement card\"},{\"linkId\":\"0cbe66db-ff12-473a-940e-4672fb82de44\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has received a MedsCheck, Diabetes MedsCheck, Home Medicines Review (HMR) otr Restidential Medication Management Review (RMMR) in the past 12 months\"},{\"linkId\":\"35790cfd-2d98-4721-963e-9663e1897a17\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient is living at home in a community setting\"},{\"linkId\":\"3ccc8304-76cd-41ff-9360-2c8755590bae\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has been recently diagnosed with type 3 diabetes (in the last 12 months) AND is unable to gain timely access to existing diabetes education or health services in the community OR \"},{\"linkId\":\"b05f6f09-49ec-40f9-a889-9a3fdff9e0da\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has type 2 diabetes , is less than ideally controlled AND is unable to gain timely access to existing diabetes education or health services in their community \"},{\"linkId\":\"4a777f56-800d-4e0b-a9c3-e929832adb5b\",\"answer\":[{\"valueBoolean\":false,\"group\":[{\"linkId\":\"95bbc904-149e-427f-88a4-7f6c8ab186fa\",\"question\":[{\"linkId\":\"f0acea9e-716c-4fce-b7a2-aad59de9d136\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Patient has had an Acute or Adverse Event\"},{\"linkId\":\"e1629159-6dea-4295-a93e-e7c2829ce180\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Exacerbation of a Chronic Disease or Condition\"},{\"linkId\":\"2ce526fa-edaa-44b3-8d5a-6e97f6379ce8\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"New Diagnosis\"},{\"linkId\":\"9d6ffa9f-0110-418c-9ed0-f04910fda2ed\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Recent hospital admission (<3 months)\"},{\"linkId\":\"d2803ff7-25f7-4c7b-ab92-356c49910478\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Major change to regular medication regime\"},{\"linkId\":\"b34af32d-c69d-4d44-889f-5b6d420a7d08\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Suspected non-adherence to the patient's medication regime \"},{\"linkId\":\"74bad553-c273-41e6-8647-22b860430bc2\",\"answer\":[],\"text\":\"Other\"}]}]}],\"text\":\"The patient has experienced one or more of the following recent significant medical events\"},{\"linkId\":\"ecbf4e5a-d4d1-43eb-9f43-0c0e35fc09c7\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The Pharmacist has obtained patient consent to take part in the MedsCheck Service or Diabetes MedsCheck Service  and share information obtained during the services with other nominated members of the patients healthcare team (such as their GP, diabetes educator) if required\"},{\"linkId\":\"8ef66774-43b0-4190-873f-cfbb6e980aa9\",\"answer\":[],\"text\":\"Question\"}]}}}]}"; FhirValidator val = ourCtxDstu2.newValidator(); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2016may/hapi/validation/ResourceValidatorDstu2_1Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2016may/hapi/validation/ResourceValidatorDstu2_1Test.java index 58df21e0afb..5e0c0ee864b 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2016may/hapi/validation/ResourceValidatorDstu2_1Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2016may/hapi/validation/ResourceValidatorDstu2_1Test.java @@ -11,15 +11,27 @@ import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; import org.apache.commons.io.IOUtils; import org.hamcrest.core.StringContains; -import org.hl7.fhir.dstu2016may.model.*; +import org.hl7.fhir.dstu2016may.model.CodeableConcept; +import org.hl7.fhir.dstu2016may.model.Coding; +import org.hl7.fhir.dstu2016may.model.DateType; +import org.hl7.fhir.dstu2016may.model.OperationOutcome; +import org.hl7.fhir.dstu2016may.model.Patient; +import org.hl7.fhir.dstu2016may.model.Reference; +import org.hl7.fhir.dstu2016may.model.StringType; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceValidatorDstu2_1Test { @@ -60,7 +72,7 @@ public class ResourceValidatorDstu2_1Test { parser.parseResource(encoded); fail(); } catch (DataFormatException e) { - assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage()); + assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: [element=\"birthDate\"] Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage()); } } @@ -103,7 +115,7 @@ public class ResourceValidatorDstu2_1Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(messageString); @@ -124,7 +136,7 @@ public class ResourceValidatorDstu2_1Test { String input = IOUtils.toString(getClass().getResourceAsStream("/questionnaire_jon_z_20160506.xml")); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(input); @@ -170,7 +182,7 @@ public class ResourceValidatorDstu2_1Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(messageString); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/elementmodel/PropertyDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/elementmodel/PropertyDstu3Test.java index d6464ee3af7..a3c120bb395 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/elementmodel/PropertyDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/elementmodel/PropertyDstu3Test.java @@ -3,8 +3,8 @@ package org.hl7.fhir.dstu3.elementmodel; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import org.apache.commons.io.IOUtils; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.model.ElementDefinition; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.exceptions.DefinitionException; @@ -15,7 +15,8 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * Created by axemj on 14/07/2017. @@ -32,7 +33,7 @@ public class PropertyDstu3Test { final String sdString = IOUtils.toString(getClass().getResourceAsStream("/customPatientSd.xml"), StandardCharsets.UTF_8); final IParser parser = ourCtx.newXmlParser(); sd = parser.parseResource(StructureDefinition.class, sdString); - workerContext = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport()); + workerContext = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport(ourCtx)); } @Test diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupportTest.java index 378c1095459..e0ef7011fa4 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupportTest.java @@ -1,28 +1,28 @@ package org.hl7.fhir.dstu3.hapi.validation; -import static org.junit.Assert.*; - -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.junit.AfterClass; -import org.junit.Test; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.junit.AfterClass; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; public class DefaultProfileValidationSupportTest { - private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport(); + private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport(ourCtx); private static FhirContext ourCtx = FhirContext.forDstu3(); @Test public void testGetStructureDefinitionsWithRelativeUrls() { - assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "http://hl7.org/fhir/StructureDefinition/Extension")); - assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "StructureDefinition/Extension")); - assertNotNull(mySvc.fetchStructureDefinition(ourCtx, "Extension")); + assertNotNull(mySvc.fetchStructureDefinition("http://hl7.org/fhir/StructureDefinition/Extension")); + assertNotNull(mySvc.fetchStructureDefinition("StructureDefinition/Extension")); + assertNotNull(mySvc.fetchStructureDefinition("Extension")); - assertNull(mySvc.fetchStructureDefinition(ourCtx, "http://hl7.org/fhir/StructureDefinition/Extension2")); - assertNull(mySvc.fetchStructureDefinition(ourCtx, "StructureDefinition/Extension2")); - assertNull(mySvc.fetchStructureDefinition(ourCtx, "Extension2")); + assertNull(mySvc.fetchStructureDefinition("http://hl7.org/fhir/StructureDefinition/Extension2")); + assertNull(mySvc.fetchStructureDefinition("StructureDefinition/Extension2")); + assertNull(mySvc.fetchStructureDefinition("Extension2")); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index 29fbca55962..212f78b1418 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -1,7 +1,9 @@ package org.hl7.fhir.dstu3.hapi.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; @@ -9,9 +11,11 @@ import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; @@ -20,36 +24,59 @@ import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu3.utils.FHIRPathEngine; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r5.utils.IResourceValidator; -import org.junit.*; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import org.mockito.internal.matchers.Any; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import java.util.zip.GZIPInputStream; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class FhirInstanceValidatorDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorDstu3Test.class); - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forDstu3(); + private static IValidationSupport myDefaultValidationSupport = ourCtx.getValidationSupport(); @Rule public TestRule watcher = new TestWatcher() { @Override @@ -58,7 +85,6 @@ public class FhirInstanceValidatorDstu3Test { } }; private FhirInstanceValidator myInstanceVal; - private IValidationSupport myMockSupport; private Map mySupportedCodeSystemsForExpansion; private FhirValidator myVal; private ArrayList myValidConcepts; @@ -67,6 +93,7 @@ public class FhirInstanceValidatorDstu3Test { private HashMap myCodeSystems; private HashMap myValueSets; private HashMap myQuestionnaires; + private CachingValidationSupport myValidationSupport; private void addValidConcept(String theSystem, String theCode) { myValidSystems.add(theSystem); @@ -80,9 +107,14 @@ public class FhirInstanceValidatorDstu3Test { myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - myMockSupport = mock(IValidationSupport.class); - CachingValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(myMockSupport, myDefaultValidationSupport)); - myInstanceVal = new FhirInstanceValidator(validationSupport); + IValidationSupport mockSupport = mock(IValidationSupport.class); + when(mockSupport.getFhirContext()).thenReturn(ourCtx); + myValidationSupport = new CachingValidationSupport(new ValidationSupportChain( + mockSupport, + myDefaultValidationSupport, + new InMemoryTerminologyServerValidationSupport(ourCtx), + new CommonCodeSystemsTerminologyService(ourCtx))); + myInstanceVal = new FhirInstanceValidator(myValidationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -90,36 +122,37 @@ public class FhirInstanceValidatorDstu3Test { myValidConcepts = new ArrayList<>(); - when(myMockSupport.expandValueSet(any(FhirContext.class), nullable(ConceptSetComponent.class))).thenAnswer(new Answer() { + when(mockSupport.expandValueSet(any(), nullable(ValueSetExpansionOptions.class), nullable(IBaseResource.class))).thenAnswer(new Answer() { @Override public ValueSetExpansionComponent answer(InvocationOnMock theInvocation) { - ConceptSetComponent arg = (ConceptSetComponent) theInvocation.getArguments()[0]; - ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getSystem()); + ValueSet arg = (ValueSet) theInvocation.getArgument(0, IBaseResource.class); + ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getCompose().getIncludeFirstRep().getSystem()); if (retVal == null) { - retVal = myDefaultValidationSupport.expandValueSet(nullable(FhirContext.class), arg); + ValueSet expandedVs = (ValueSet) myDefaultValidationSupport.expandValueSet(myDefaultValidationSupport, null, arg).getValueSet(); + retVal = expandedVs.getExpansion(); } - ourLog.debug("expandValueSet({}) : {}", new Object[] {theInvocation.getArguments()[0], retVal}); + ourLog.debug("expandValueSet({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal}); return retVal; } }); - when(myMockSupport.isCodeSystemSupported(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.isCodeSystemSupported(any(), nullable(String.class))).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock theInvocation) { String url = (String) theInvocation.getArguments()[1]; boolean retVal = myValidSystems.contains(url); - ourLog.debug("isCodeSystemSupported({}) : {}", new Object[] {url, retVal}); + ourLog.debug("isCodeSystemSupported({}) : {}", new Object[]{url, retVal}); if (retVal == false) { retVal = myCodeSystems.containsKey(url); } return retVal; } }); - when(myMockSupport.fetchResource(nullable(FhirContext.class), nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchResource(nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { @Override public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable { IBaseResource retVal = null; - Class type = (Class) theInvocation.getArguments()[1]; - String id = (String) theInvocation.getArguments()[2]; + Class type = (Class) theInvocation.getArguments()[0]; + String id = (String) theInvocation.getArguments()[1]; if ("Questionnaire/q_jon".equals(id)) { retVal = ourCtx.newJsonParser().parseResource(IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/q_jon.json"), Charsets.UTF_8)); } else { @@ -138,7 +171,7 @@ public class FhirInstanceValidatorDstu3Test { } if (retVal == null) { - retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class) theInvocation.getArguments()[1], id); + retVal = myDefaultValidationSupport.fetchResource((Class) theInvocation.getArguments()[0], id); } } if (retVal == null) { @@ -147,38 +180,40 @@ public class FhirInstanceValidatorDstu3Test { return retVal; } }); - when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.validateCode(any(), any(), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { @Override - public IContextValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { - FhirContext ctx = theInvocation.getArgument(0, FhirContext.class); - String system = theInvocation.getArgument(1, String.class); - String code = theInvocation.getArgument(2, String.class); - String display = theInvocation.getArgument(3, String.class); - String valueSetUrl = theInvocation.getArgument(4, String.class); - IContextValidationSupport.CodeValidationResult retVal; + public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { + ConceptValidationOptions options = theInvocation.getArgument(1, ConceptValidationOptions.class); + String system = theInvocation.getArgument(2, String.class); + String code = theInvocation.getArgument(3, String.class); + String display = theInvocation.getArgument(4, String.class); + String valueSetUrl = theInvocation.getArgument(5, String.class); + IValidationSupport.CodeValidationResult retVal; if (myValidConcepts.contains(system + "___" + code)) { - retVal = new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code))); + retVal = new IValidationSupport.CodeValidationResult().setCode(code); + } else if (myValidSystems.contains(system)) { + return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.WARNING.toCode()).setMessage("Unknown code: " + system + " / " + code); } else if (myCodeSystems.containsKey(system)) { CodeSystem cs = myCodeSystems.get(system); Optional found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst(); - retVal = found.map(t->new IContextValidationSupport.CodeValidationResult(t)).orElse(null); + retVal = found.map(t -> new IValidationSupport.CodeValidationResult().setCode(t.getCode())).orElse(null); } else { - retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl); + retVal = myDefaultValidationSupport.validateCode(myDefaultValidationSupport, options, system, code, display, valueSetUrl); } ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal); return retVal; } }); - when(myMockSupport.fetchCodeSystem(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchCodeSystem(nullable(String.class))).thenAnswer(new Answer() { @Override public CodeSystem answer(InvocationOnMock theInvocation) { CodeSystem retVal; - String id = (String) theInvocation.getArguments()[1]; + String id = (String) theInvocation.getArguments()[0]; retVal = myCodeSystems.get(id); if (retVal == null) { - retVal = myDefaultValidationSupport.fetchCodeSystem((FhirContext) theInvocation.getArguments()[0], id); + retVal = (CodeSystem) myDefaultValidationSupport.fetchCodeSystem(id); } if (retVal == null) { @@ -191,23 +226,23 @@ public class FhirInstanceValidatorDstu3Test { myValueSets = new HashMap<>(); myCodeSystems = new HashMap<>(); myQuestionnaires = new HashMap<>(); - when(myMockSupport.fetchStructureDefinition(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchStructureDefinition(nullable(String.class))).thenAnswer(new Answer() { @Override public StructureDefinition answer(InvocationOnMock theInvocation) { String url = (String) theInvocation.getArguments()[1]; StructureDefinition retVal = myStructureDefinitions.get(url); if (retVal == null) { - retVal = myDefaultValidationSupport.fetchStructureDefinition((FhirContext) theInvocation.getArguments()[0], url); + retVal = (StructureDefinition) myDefaultValidationSupport.fetchStructureDefinition(url); } - ourLog.info("fetchStructureDefinition({}) : {}", new Object[] {url, retVal}); + ourLog.info("fetchStructureDefinition({}) : {}", new Object[]{url, retVal}); return retVal; } }); - when(myMockSupport.fetchAllStructureDefinitions(nullable(FhirContext.class))).thenAnswer(new Answer>() { + when(mockSupport.fetchAllStructureDefinitions()).thenAnswer(new Answer>() { @Override public List answer(InvocationOnMock theInvocation) { - List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions((FhirContext) theInvocation.getArguments()[0]); - ourLog.debug("fetchAllStructureDefinitions()", new Object[] {}); + List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions(); + ourLog.debug("fetchAllStructureDefinitions()", new Object[]{}); return retVal; } }); @@ -242,7 +277,7 @@ public class FhirInstanceValidatorDstu3Test { } private List logResultsAndReturnNonInformationalOnes(ValidationResult theOutput) { - List retVal = new ArrayList(); + List retVal = new ArrayList<>(); int index = 0; for (SingleValidationMessage next : theOutput.getMessages()) { @@ -274,7 +309,7 @@ public class FhirInstanceValidatorDstu3Test { procedure.setPerformed(period); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(procedure); @@ -321,7 +356,7 @@ public class FhirInstanceValidatorDstu3Test { myCodeSystems.put(csBinderRecommended.getUrl(), csBinderRecommended); ValueSet vsBinderRequired = loadResource("/dstu3/fmc01-vs-binderrecommended.json", ValueSet.class); myValueSets.put(vsBinderRequired.getUrl(), vsBinderRequired); - myValueSets.put("ValueSet/" +vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); + myValueSets.put("ValueSet/" + vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); ValueSet vsYesNo = loadResource("/dstu3/fmc01-vs-yesnounk.json", ValueSet.class); myValueSets.put(vsYesNo.getUrl(), vsYesNo); myValueSets.put("ValueSet/" + vsYesNo.getIdElement().getIdPart(), vsYesNo); @@ -344,7 +379,7 @@ public class FhirInstanceValidatorDstu3Test { ValueSet vsBinderRequired = loadResource("/dstu3/fmc03-vs-binderrecommend.json", ValueSet.class); myValueSets.put(vsBinderRequired.getUrl(), vsBinderRequired); - myValueSets.put("ValueSet/" +vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); + myValueSets.put("ValueSet/" + vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); ValueSet vsYesNo = loadResource("/dstu3/fmc03-vs-fmcyesno.json", ValueSet.class); myValueSets.put(vsYesNo.getUrl(), vsYesNo); myValueSets.put("ValueSet/" + vsYesNo.getIdElement().getIdPart(), vsYesNo); @@ -366,7 +401,7 @@ public class FhirInstanceValidatorDstu3Test { myCodeSystems.put(csBinderRecommended.getUrl(), csBinderRecommended); ValueSet vsBinderRequired = loadResource("/dstu3/fmc02-vs-binderrecomm.json", ValueSet.class); myValueSets.put(vsBinderRequired.getUrl(), vsBinderRequired); - myValueSets.put("ValueSet/" +vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); + myValueSets.put("ValueSet/" + vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); ValueSet vsYesNo = loadResource("/dstu3/fmc01-vs-yesnounk.json", ValueSet.class); myValueSets.put(vsYesNo.getUrl(), vsYesNo); myValueSets.put("ValueSet/" + vsYesNo.getIdElement().getIdPart(), vsYesNo); @@ -376,7 +411,7 @@ public class FhirInstanceValidatorDstu3Test { QuestionnaireResponse qr = loadResource("/dstu3/fmc02-questionnaireresponse-01.json", QuestionnaireResponse.class); ValidationResult result = myVal.validateWithResult(qr); List errors = logResultsAndReturnNonInformationalOnes(result); - assertThat(errors.get(0).getMessage(), containsString("Item has answer, even though it is not enabled (item id = 'BO_ConsDrop')")); + assertThat(errors.get(0).getMessage(), containsString("Item has answer, even though it is not enabled (item id = \"BO_ConsDrop\")")); assertEquals(1, errors.size()); } @@ -388,7 +423,7 @@ public class FhirInstanceValidatorDstu3Test { myCodeSystems.put(csBinderRecommended.getUrl(), csBinderRecommended); ValueSet vsBinderRequired = loadResource("/dstu3/fmc02-vs-binderrecomm.json", ValueSet.class); myValueSets.put(vsBinderRequired.getUrl(), vsBinderRequired); - myValueSets.put("ValueSet/" +vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); + myValueSets.put("ValueSet/" + vsBinderRequired.getIdElement().getIdPart(), vsBinderRequired); ValueSet vsYesNo = loadResource("/dstu3/fmc01-vs-yesnounk.json", ValueSet.class); myValueSets.put(vsYesNo.getUrl(), vsYesNo); myValueSets.put("ValueSet/" + vsYesNo.getIdElement().getIdPart(), vsYesNo); @@ -608,8 +643,9 @@ public class FhirInstanceValidatorDstu3Test { ValidationResult results = myVal.validateWithResult(is); List outcome = logResultsAndReturnNonInformationalOnes(results); - assertEquals(2, outcome.size()); - assertEquals("Unknown code: http://dicom.nema.org/resources/ontology/DCM / BAR", outcome.get(0).getMessage()); + assertEquals(1, outcome.size()); + assertEquals("Unknown code 'http://dicom.nema.org/resources/ontology/DCM#BAR' for \"http://dicom.nema.org/resources/ontology/DCM#BAR\"", outcome.get(0).getMessage()); +// assertEquals("The Coding provided is not in the value set http://hl7.org/fhir/ValueSet/dicom-cid29, and a code should come from this value set unless it has no suitable code. (error message = Unknown code[BAR] in system[http://dicom.nema.org/resources/ontology/DCM])", outcome.get(1).getMessage()); } @@ -827,31 +863,6 @@ public class FhirInstanceValidatorDstu3Test { assertEquals(output.toString(), 0, res.size()); } - @Test - public void testValidateRawXmlWithMissingRootNamespace() { - //@formatter:off - String input = "" - + "" - + " " - + " " - + "
        Some narrative
        " - + "
        " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + "
        "; - //@formatter:on - - ValidationResult output = myVal.validateWithResult(input); - assertEquals(output.toString(), 1, output.getMessages().size()); - assertEquals("This 'Patient' cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); - ourLog.info(output.getMessages().get(0).getLocationString()); - } - /** * A reference with only an identifier should be valid */ @@ -933,7 +944,7 @@ public class FhirInstanceValidatorDstu3Test { input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnAll(output); @@ -953,7 +964,7 @@ public class FhirInstanceValidatorDstu3Test { input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); @@ -973,10 +984,10 @@ public class FhirInstanceValidatorDstu3Test { input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); input.setStatus(ObservationStatus.FINAL); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); - assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); + assertThat(errors.toString(), containsString("Profile reference \"http://foo/structuredefinition/myprofile\" could not be resolved, so has not been checked")); } @Test @@ -1008,7 +1019,7 @@ public class FhirInstanceValidatorDstu3Test { @Test public void testValidateResourceWithDefaultValuesetBadCode() { - //@formatter:off + String input = "\n" + " \n" + @@ -1016,11 +1027,11 @@ public class FhirInstanceValidatorDstu3Test { " \n" + " \n" + ""; - //@formatter:on + ValidationResult output = myVal.validateWithResult(input); logResultsAndReturnAll(output); assertEquals( - "The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code[notvalidcode] in system[(none)])", + "The value provided (\"notvalidcode\") is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code 'notvalidcode')", output.getMessages().get(0).getMessage()); } @@ -1028,7 +1039,7 @@ public class FhirInstanceValidatorDstu3Test { public void testValidateResourceWithExampleBindingCodeValidationFailing() { Observation input = new Observation(); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); @@ -1043,7 +1054,7 @@ public class FhirInstanceValidatorDstu3Test { public void testValidateResourceWithExampleBindingCodeValidationFailingNonLoinc() { Observation input = new Observation(); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1060,7 +1071,7 @@ public class FhirInstanceValidatorDstu3Test { public void testValidateResourceWithExampleBindingCodeValidationPassingLoinc() { Observation input = new Observation(); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1079,7 +1090,7 @@ public class FhirInstanceValidatorDstu3Test { expansionComponent.addContains().setSystem("http://loinc.org").setCode("12345").setDisplay("Some display code"); mySupportedCodeSystemsForExpansion.put("http://loinc.org", expansionComponent); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1095,7 +1106,7 @@ public class FhirInstanceValidatorDstu3Test { public void testValidateResourceWithExampleBindingCodeValidationPassingNonLoinc() { Observation input = new Observation(); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1151,7 +1162,7 @@ public class FhirInstanceValidatorDstu3Test { String input = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/dstu3-rick-test.json"), Charsets.UTF_8); IResourceValidator.IValidatorResourceFetcher resourceFetcher = mock(IResourceValidator.IValidatorResourceFetcher.class); - when(resourceFetcher.validationPolicy(any(),anyString(), anyString())).thenReturn(IResourceValidator.ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); + when(resourceFetcher.validationPolicy(any(), anyString(), anyString())).thenReturn(IResourceValidator.ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); myInstanceVal.setValidatorResourceFetcher(resourceFetcher); myVal.validateWithResult(input); @@ -1173,8 +1184,8 @@ public class FhirInstanceValidatorDstu3Test { @AfterClass public static void afterClassClearContext() { - myDefaultValidationSupport.flush(); myDefaultValidationSupport = null; + ourCtx = null; TestUtil.clearAllStaticFieldsForUnitTest(); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java index 7cbd9968f1e..cff953a5bdc 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java @@ -1,7 +1,8 @@ package org.hl7.fhir.dstu3.hapi.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; @@ -9,11 +10,11 @@ import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; import org.hamcrest.Matchers; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemOptionComponent; @@ -22,6 +23,7 @@ import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemA import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.AfterClass; import org.junit.Before; @@ -33,14 +35,22 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE; import static org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.nullable; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; public class QuestionnaireResponseValidatorDstu3Test { private static final String QUESTIONNAIRE_URL = "http://example.com/Questionnaire/q1"; @@ -49,26 +59,22 @@ public class QuestionnaireResponseValidatorDstu3Test { private static final String CODE_ICC_SCHOOLTYPE_PT = "PT"; private static final IdType ID_VS_SCHOOLTYPE = new IdType("ValueSet/schooltype"); private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype"; - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); - private static FhirContext ourCtx; + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); private FhirInstanceValidator myInstanceVal; private FhirValidator myVal; private IValidationSupport myValSupport; - - @BeforeClass - public static void beforeClass() { - ourCtx = FhirContext.forDstu3(); - } - + @Before public void before() { myValSupport = mock(IValidationSupport.class); + when(myValSupport.getFhirContext()).thenReturn(ourCtx); myVal = ourCtx.newValidator(); myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport); + ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport, new InMemoryTerminologyServerValidationSupport(ourCtx), new CommonCodeSystemsTerminologyService(ourCtx)); myInstanceVal = new FhirInstanceValidator(validationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -135,18 +141,19 @@ public class QuestionnaireResponseValidatorDstu3Test { answerValues[15] = new Quantity(42); for (int i = 0; i < itemCnt; i++) { + before(); + if (questionnaireItemTypes[i] == null) continue; String linkId = "link" + i; reset(myValSupport); Questionnaire q = new Questionnaire(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - myInstanceVal.flushCaches(); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); q.getItem().clear(); QuestionnaireItemComponent questionnaireItemComponent = @@ -179,7 +186,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); @@ -193,34 +200,36 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setOptions(new Reference("http://somevalueset")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode(CODE_ICC_SCHOOLTYPE_PT))); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode("warning").setMessage("Unknown code: http://codesystems.com/system / code1")); QuestionnaireResponse qa; @@ -248,6 +257,8 @@ public class QuestionnaireResponseValidatorDstu3Test { assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system / code1 - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); + // Unhandled system + qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(questionnaireRef); @@ -255,7 +266,7 @@ public class QuestionnaireResponseValidatorDstu3Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3")); + assertThat(errors.toString(), containsString("Unknown code 'http://codesystems.com/system2#code3'")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); } @@ -272,47 +283,47 @@ public class QuestionnaireResponseValidatorDstu3Test { QuestionnaireResponseItemComponent qaGroup = qa.addItem(); qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("Element 'QuestionnaireResponse.item[0].linkId': minimum required = 1, but only found 0")); } - + @Test public void testMissingAnswerInNestedStructureIsReported() { Questionnaire q = new Questionnaire(); q.addItem().setType(QuestionnaireItemType.GROUP).setRequired(true) - .addItem().setType(QuestionnaireItemType.GROUP).setRequired(true) - .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("link0").setRequired(true); - + .addItem().setType(QuestionnaireItemType.GROUP).setRequired(true) + .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("link0").setRequired(true); + QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); - - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); - + + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qa); - + assertThat(errors.toString(), Matchers.not(containsString("No issues"))); } - + @Test public void testGroupMarkedAsRequiredIsOk() { Questionnaire q = new Questionnaire(); q.addItem().setType(QuestionnaireItemType.GROUP).setRequired(true).setLinkId("group") - .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("child").setRequired(true); - + .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("child").setRequired(true); + QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); qa.addItem().setLinkId("group") - .addItem().setLinkId("child").addAnswer().setValue(new BooleanType(true)); - - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); - + .addItem().setLinkId("child").addAnswer().setValue(new BooleanType(true)); + + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qa); - + assertThat(errors.toString(), containsString("No issues")); } @@ -328,7 +339,7 @@ public class QuestionnaireResponseValidatorDstu3Test { QuestionnaireResponseItemComponent qaItem = qa.addItem().setLinkId("link0"); qaItem.addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -349,62 +360,62 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No response found for required item with id = 'link0'")); } - + @Test public void testEnableWhenWithHasAnswerTrueDisablesQuestionWhenNoAnswerIsPresent() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); q.addItem().setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING).addEnableWhen().setQuestion("link0").setHasAnswer(true); - + QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionHasAnswerTrue() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); - + // create the questionnaire QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING).addEnableWhen().setQuestion("link0").setHasAnswer(true); q.addItem(item1); - + QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); - + String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testRequiredQuestionWithEnableWhenHidesQuestion() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); - + // create the questionnaire QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setType(QuestionnaireItemType.STRING).setRequired(true); @@ -420,19 +431,19 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionValue() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); - + //link1 question is enabled when link0 has answer QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING); @@ -448,18 +459,18 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L)); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testRequiredQuestionQuantityWithEnableWhenEnablesQuestionValue() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); - + //link1 question is enabled when link0 has answer QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setType(QuestionnaireItemType.STRING).setRequired(true); @@ -475,7 +486,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L)); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -500,7 +511,7 @@ public class QuestionnaireResponseValidatorDstu3Test { .setQuestion("link0") .setAnswer(new Coding("http://foo", "YES", null)); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q); QuestionnaireResponse qa; ValidationResult errors; @@ -521,7 +532,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("HELLO")); errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Item has answer, even though it is not enabled (item id = 'link1')")); + assertThat(errors.toString(), containsString("Item has answer, even though it is not enabled (item id = \"link1\")")); // link0 has an answer, and it's the right one qa = new QuestionnaireResponse(); @@ -538,7 +549,7 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.STRING); - + // create the questionnaire QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING); @@ -555,20 +566,19 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("BAR")); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - - + @Test public void testRequiredQuestionWithEnableWheHidesRequiredQuestionnHasAnswerFalse() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); - + // create the questionnaire QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING); @@ -581,58 +591,58 @@ public class QuestionnaireResponseValidatorDstu3Test { QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); - + // link1 should be disabled, because the enableWhen enables it when link0 doesn't haven an answer qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); String reference = qa.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testGivenQuestionIsNotEnabledWithEnableWhenAnswersAreReportedAsErrors() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); q.addItem().setLinkId("link2").setRequired(false).setType(QuestionnaireItemType.STRING).addEnableWhen().setQuestion("link0").setHasAnswer(true); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(QuestionnaireResponseStatus.COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); - + qr.addItem().setLinkId("link2").addAnswer().setValue(new StringType("FOO")); - + String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); - + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qr); - + assertThat(errors.toString(), Matchers.not(containsString("No issues"))); } - + @Test public void testGivenQuestionIsNotEnabledWithEnableWhenButHasItemsWithoutAnswersAreOk() { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); q.addItem().setLinkId("link2").setRequired(false).setType(QuestionnaireItemType.STRING).addEnableWhen().setQuestion("link0").setHasAnswer(true); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(QuestionnaireResponseStatus.COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); - + qr.addItem().setLinkId("link2"); - + String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); - + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qr); - + assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testGivenQuestionIsNotEnabledWithEnableWhenButHasItemsWithoutAnswersAreOk2() { Questionnaire q = new Questionnaire(); @@ -644,7 +654,7 @@ public class QuestionnaireResponseValidatorDstu3Test { .setRequired(true) .setType(QuestionnaireItemType.STRING) .addEnableWhen().setQuestion("link1").setHasAnswer(true); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(QuestionnaireResponseStatus.COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); @@ -653,9 +663,9 @@ public class QuestionnaireResponseValidatorDstu3Test { qr.addItem().setLinkId("link1").addAnswer().setValue(new StringType("Answer")); qr.addItem().setLinkId("link2"); - + String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); // Without an answer ValidationResult errors = myVal.validateWithResult(qr); @@ -666,28 +676,28 @@ public class QuestionnaireResponseValidatorDstu3Test { errors = myVal.validateWithResult(qr); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testGivenQuestionnaireResponseHasSiblingItemsWhenTheyShouldBeChildItems() { Questionnaire q = new Questionnaire(); QuestionnaireItemComponent item = q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.GROUP); item.addItem().setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(QuestionnaireResponseStatus.COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); qr.addItem().setLinkId("link0").setText("Text"); qr.addItem().setLinkId("link1").addAnswer().setValue(new StringType("Answer")); String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); - + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qr); assertThat(errors.toString(), Matchers.not(containsString("No issues"))); assertTrue("Must contain structural error about misplaced link1 item", - errors.getMessages().stream().filter(vm -> vm.getMessage().contains("Structural Error")) - .anyMatch(vm -> vm.getMessage().contains("link1"))); + errors.getMessages().stream().filter(vm -> vm.getMessage().contains("Structural Error")) + .anyMatch(vm -> vm.getMessage().contains("link1"))); } - + @Test public void testAnswerIsValueCodingWithExtensionInside() { Questionnaire q = new Questionnaire(); @@ -695,7 +705,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qcoding.setCode("1293"); q.addItem().setLinkId("1B").setRequired(true).setType(CHOICE).addOption().setValue(qcoding); q.addItem().setLinkId("2B").setType(BOOLEAN).addEnableWhen().setQuestion("1B").setAnswer(qcoding); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); @@ -706,15 +716,15 @@ public class QuestionnaireResponseValidatorDstu3Test { answer.setValue(coding); coding.addExtension("http://hl7.org/fhir/StructureDefinition/iso21090-CO-value", new DecimalType("1.0")); qr.addItem().setLinkId("2B").addAnswer().setValue(new BooleanType(true)); - + String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); - + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qr); assertThat(errors.toString(), containsString("No issues")); - + } - + @Test public void testChoiceItemsEnableWhenHasNoSystemYetAnswerHasSystem() { Questionnaire q = new Questionnaire(); @@ -725,7 +735,7 @@ public class QuestionnaireResponseValidatorDstu3Test { Coding enablewhenCoding = new Coding(); enablewhenCoding.setCode("male"); q.addItem().setLinkId("2B").setType(BOOLEAN).addEnableWhen().setQuestion("1B").setAnswer(enablewhenCoding); - + QuestionnaireResponse qr = new QuestionnaireResponse(); qr.setStatus(COMPLETED); qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL); @@ -736,14 +746,14 @@ public class QuestionnaireResponseValidatorDstu3Test { QuestionnaireResponseItemAnswerComponent answer = qrItem.addAnswer(); answer.setValue(coding); qr.addItem().setLinkId("2B").addAnswer().setValue(new BooleanType(true)); - + String reference = qr.getQuestionnaire().getReference(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); - + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qr); assertThat(errors.toString(), containsString("No issues")); } - + @Test public void testEmbeddedItemInChoice() { String questionnaireRef = QUESTIONNAIRE_URL; @@ -762,22 +772,21 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) - .thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))).thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue)))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -818,22 +827,22 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))) .thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue)))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -870,7 +879,7 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); @@ -883,7 +892,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.getQuestionnaire().setReference(questionnaireRef); qa.addItem().setLinkId("link1") .addAnswer() - .addItem().setLinkId("link11"); + .addItem().setLinkId("link11"); String rXml = xmlParser.encodeResourceToString(qa); ourLog.info(rXml); @@ -944,10 +953,10 @@ public class QuestionnaireResponseValidatorDstu3Test { .addAnswer() .setValue(new Coding(SYSTEMURI_ICC_SCHOOLTYPE, CODE_ICC_SCHOOLTYPE_PT, "")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireResponse.getQuestionnaire().getReference()))).thenReturn(questionnaire); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE.getValue()))).thenReturn(iccSchoolTypeVs); - when(myValSupport.validateCodeInValueSet(any(), eq(SYSTEMURI_ICC_SCHOOLTYPE), eq(CODE_ICC_SCHOOLTYPE_PT), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode(CODE_ICC_SCHOOLTYPE_PT))); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireResponse.getQuestionnaire().getReference()))).thenReturn(questionnaire); + when(myValSupport.fetchResource(eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE.getValue()))).thenReturn(iccSchoolTypeVs); + when(myValSupport.validateCodeInValueSet(any(), any(), eq(SYSTEMURI_ICC_SCHOOLTYPE), eq(CODE_ICC_SCHOOLTYPE_PT), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); ValidationResult errors = myVal.validateWithResult(questionnaireResponse); @@ -963,7 +972,7 @@ public class QuestionnaireResponseValidatorDstu3Test { .setRequired(true); String reference = QUESTIONNAIRE_URL; - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))) .thenReturn(q); QuestionnaireResponse qa = new QuestionnaireResponse(); @@ -991,34 +1000,34 @@ public class QuestionnaireResponseValidatorDstu3Test { Questionnaire q = new Questionnaire(); QuestionnaireItemComponent item = q.addItem(); item.setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.OPENCHOICE).setOptions(new Reference("http://somevalueset")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.setUrl("http://somevalueset"); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCodeInValueSet(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(), any(IBaseResource.class))).thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0")))); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any(IBaseResource.class))).thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); QuestionnaireResponse qa; ValidationResult errors; @@ -1042,7 +1051,7 @@ public class QuestionnaireResponseValidatorDstu3Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)")); + assertThat(errors.toString(), containsString("Unknown code for \"http://codesystems.com/system#code1\"")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); // Partial code @@ -1118,7 +1127,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -1136,7 +1145,7 @@ public class QuestionnaireResponseValidatorDstu3Test { qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL); qa.addItem().setLinkId("link1").addItem().setLinkId("link2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -1144,6 +1153,11 @@ public class QuestionnaireResponseValidatorDstu3Test { assertThat(errors.toString(), containsString("LinkId \"link1\" not found in questionnaire")); } + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forDstu3(); + } + @AfterClass public static void afterClassClearContext() { myDefaultValidationSupport.flush(); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java index 9e8a25f5a42..825b8963fc2 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java @@ -1,18 +1,21 @@ package org.hl7.fhir.dstu3.hapi.validation; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; import org.hamcrest.Matchers; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; import org.hl7.fhir.dstu3.model.Questionnaire; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; @@ -26,23 +29,27 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class QuestionnaireValidatorDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorDstu3Test.class); - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forDstu3(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); private FhirInstanceValidator myInstanceVal; private FhirValidator myVal; @Before public void before() { + + IValidationSupport myValSupport = mock(IValidationSupport.class); + when(myValSupport.getFhirContext()).thenReturn(ourCtx); myVal = ourCtx.newValidator(); myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport); + ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport, new InMemoryTerminologyServerValidationSupport(ourCtx)); myInstanceVal = new FhirInstanceValidator(validationSupport); myVal.registerValidatorModule(myInstanceVal); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java index 98b8429cd34..967315b17bf 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java @@ -1,20 +1,26 @@ package org.hl7.fhir.dstu3.hapi.validation; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.validation.*; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ResultSeverityEnum; +import ca.uhn.fhir.validation.SchemaBaseValidator; +import ca.uhn.fhir.validation.SingleValidationMessage; +import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; import org.hamcrest.core.StringContains; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.conformance.ProfileUtilities; import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Condition.ConditionClinicalStatus; @@ -22,6 +28,7 @@ import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus; import org.hl7.fhir.dstu3.model.EligibilityResponse.BenefitComponent; import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.AfterClass; import org.junit.Ignore; @@ -33,8 +40,13 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceValidatorDstu3Test { @@ -86,7 +98,7 @@ public class ResourceValidatorDstu3Test { parser.parseResource(encoded); fail(); } catch (DataFormatException e) { - assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage()); + assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: [element=\"birthDate\"] Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage()); } } @@ -104,7 +116,7 @@ public class ResourceValidatorDstu3Test { String encoded = parser.encodeResourceToString(careTeam); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(encoded); @@ -148,7 +160,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult output = val.validateWithResult(p); List all = logResultsAndReturnNonInformationalOnes(output); @@ -165,7 +177,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult output = val.validateWithResult(p); List all = logResultsAndReturnNonInformationalOnes(output); @@ -176,8 +188,8 @@ public class ResourceValidatorDstu3Test { @Test @Ignore public void testValidateProfileWithExtension() throws IOException, FHIRException { - PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport(); - DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); + PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport(ourCtx); + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ourCtx); ValidationSupportChain support = new ValidationSupportChain(valSupport, defaultSupport); // Prepopulate SDs @@ -198,7 +210,7 @@ public class ResourceValidatorDstu3Test { private StructureDefinition loadStructureDefinition(DefaultProfileValidationSupport theDefaultValSupport, String theResName) throws IOException, FHIRException { StructureDefinition derived = ourCtx.newXmlParser().parseResource(StructureDefinition.class, IOUtils.toString(ResourceValidatorDstu3Test.class.getResourceAsStream(theResName))); - StructureDefinition base = theDefaultValSupport.fetchStructureDefinition(ourCtx, derived.getBaseDefinition()); + StructureDefinition base = (StructureDefinition) theDefaultValSupport.fetchStructureDefinition(derived.getBaseDefinition()); Validate.notNull(base); IWorkerContext worker = new HapiWorkerContext(ourCtx, theDefaultValSupport); @@ -222,7 +234,7 @@ public class ResourceValidatorDstu3Test { String input = ourCtx.newXmlParser().encodeResourceToString(fhirObj); FhirValidator validator = ourCtx.newValidator(); - validator.registerValidatorModule(new FhirInstanceValidator()); + validator.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = validator.validateWithResult(input); // we should get some results, not an exception @@ -253,7 +265,7 @@ public class ResourceValidatorDstu3Test { "}"; FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult output = val.validateWithResult(input); OperationOutcome operationOutcome = (OperationOutcome) output.toOperationOutcome(); @@ -273,7 +285,7 @@ public class ResourceValidatorDstu3Test { String input = IOUtils.toString(getClass().getResourceAsStream("/questionnaire_jon_z_20160506.xml"), StandardCharsets.UTF_8); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(input); @@ -314,7 +326,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(q); @@ -356,7 +368,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(encoded); @@ -402,7 +414,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(messageString); @@ -453,7 +465,7 @@ public class ResourceValidatorDstu3Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); - val.registerValidatorModule(new FhirInstanceValidator()); + val.registerValidatorModule(new FhirInstanceValidator(ourCtx)); ValidationResult result = val.validateWithResult(messageString); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/StructureMapTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/StructureMapTest.java index 0c974214ace..8d28d47c0c3 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/StructureMapTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/StructureMapTest.java @@ -1,324 +1,324 @@ package org.hl7.fhir.dstu3.hapi.validation; -import static org.junit.Assert.assertEquals; - -import java.util.*; - -import javax.annotation.Nullable; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.utils.StructureMapUtilities; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; public class StructureMapTest { - /** - * The logger object. - */ - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StructureMapTest.class); + /** + * The logger object. + */ + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StructureMapTest.class); - /** - * The basic fhir context used to parse structure definitions. - */ - FhirContext context; + /** + * The basic fhir context used to parse structure definitions. + */ + FhirContext myCtx; - /** - * HapiFhirContext used when building strucutre map utilities. - */ - IWorkerContext hapiContext; + /** + * HapiFhirContext used when building strucutre map utilities. + */ + IWorkerContext hapiContext; - /** - * path to the files used to test the profile generator. - */ - String resourcePath = null; + /** + * path to the files used to test the profile generator. + */ + String resourcePath = null; - /** - * Used to validate definitions as well as add new structure definitions to a registry. - */ - PrePopulatedValidationSupport validationSupport; + /** + * Used to validate definitions as well as add new structure definitions to a registry. + */ + ValidationSupportChain validationSupport; - public StructureMap.StructureMapGroupRuleSourceComponent buildSource(String context, @Nullable String element, @Nullable String variable, @Nullable String type, @Nullable Integer min, - @Nullable String max) { - StructureMap.StructureMapGroupRuleSourceComponent retVal = new StructureMap.StructureMapGroupRuleSourceComponent(); - retVal.setContext(context); - if (element != null) - retVal.setElement(element); - if (variable != null) - retVal.setVariable(variable); - if (type != null) - retVal.setType(type); - if (min != null) - retVal.setMin(min); - if (max != null) - retVal.setMax(max); - return retVal; - } + public StructureMap.StructureMapGroupRuleSourceComponent buildSource(String context, @Nullable String element, @Nullable String variable, @Nullable String type, @Nullable Integer min, + @Nullable String max) { + StructureMap.StructureMapGroupRuleSourceComponent retVal = new StructureMap.StructureMapGroupRuleSourceComponent(); + retVal.setContext(context); + if (element != null) + retVal.setElement(element); + if (variable != null) + retVal.setVariable(variable); + if (type != null) + retVal.setType(type); + if (min != null) + retVal.setMin(min); + if (max != null) + retVal.setMax(max); + return retVal; + } - public List buildSourceList(StructureMap.StructureMapGroupRuleSourceComponent[] sources) { - List retVal = new ArrayList(); - retVal.addAll(Arrays.asList(sources)); - return retVal; - } + public List buildSourceList(StructureMap.StructureMapGroupRuleSourceComponent[] sources) { + List retVal = new ArrayList(); + retVal.addAll(Arrays.asList(sources)); + return retVal; + } - public StructureMap.StructureMapGroupRuleTargetComponent buildTarget(@Nullable String context, @Nullable String element, @Nullable String variable, - @Nullable StructureMap.StructureMapTransform transform, @Nullable TargetParam[] params) throws Exception { - StructureMap.StructureMapGroupRuleTargetComponent retVal = new StructureMap.StructureMapGroupRuleTargetComponent(); - if (context != null) - retVal.setContext(context); - if (element != null) - retVal.setElement(element); - if (variable != null) - retVal.setVariable(variable); - if (transform != null) - retVal.setTransform(transform); - if (params != null) { - if (params.length > 0) - retVal.setParameter(this.constructParameters(params)); - } - return retVal; - } + public StructureMap.StructureMapGroupRuleTargetComponent buildTarget(@Nullable String context, @Nullable String element, @Nullable String variable, + @Nullable StructureMap.StructureMapTransform transform, @Nullable TargetParam[] params) throws Exception { + StructureMap.StructureMapGroupRuleTargetComponent retVal = new StructureMap.StructureMapGroupRuleTargetComponent(); + if (context != null) + retVal.setContext(context); + if (element != null) + retVal.setElement(element); + if (variable != null) + retVal.setVariable(variable); + if (transform != null) + retVal.setTransform(transform); + if (params != null) { + if (params.length > 0) + retVal.setParameter(this.constructParameters(params)); + } + return retVal; + } - public List buildTargetList(StructureMap.StructureMapGroupRuleTargetComponent[] sources) { - List retVal = new ArrayList(); - retVal.addAll(Arrays.asList(sources)); - return retVal; - } + public List buildTargetList(StructureMap.StructureMapGroupRuleTargetComponent[] sources) { + List retVal = new ArrayList(); + retVal.addAll(Arrays.asList(sources)); + return retVal; + } - public List buildTestGroup() throws Exception { - List retVal = new ArrayList(); - StructureMap.StructureMapGroupComponent group = new StructureMap.StructureMapGroupComponent(); - group.setName("TestStructureToCoding"); - group.setTypeMode(StructureMap.StructureMapGroupTypeMode.TYPEANDTYPES); - group.setInput(this.buildTestInput()); - group.setRule(this.buildTestRules()); - retVal.add(group); - return retVal; - } + public List buildTestGroup() throws Exception { + List retVal = new ArrayList(); + StructureMap.StructureMapGroupComponent group = new StructureMap.StructureMapGroupComponent(); + group.setName("TestStructureToCoding"); + group.setTypeMode(StructureMap.StructureMapGroupTypeMode.TYPEANDTYPES); + group.setInput(this.buildTestInput()); + group.setRule(this.buildTestRules()); + retVal.add(group); + return retVal; + } - public List buildTestInput() { - List retVal = new ArrayList(); - StructureMap.StructureMapGroupInputComponent sourceIn = new StructureMap.StructureMapGroupInputComponent(); - StructureMap.StructureMapGroupInputComponent targetIn = new StructureMap.StructureMapGroupInputComponent(); - sourceIn.setName("source"); - sourceIn.setType("TestStructure"); - sourceIn.setMode(StructureMap.StructureMapInputMode.SOURCE); - targetIn.setName("target"); - targetIn.setType("http://hl7.org/fhir/StructureDefinition/Coding"); - targetIn.setMode(StructureMap.StructureMapInputMode.TARGET); - retVal.add(sourceIn); - retVal.add(targetIn); - return retVal; - } + public List buildTestInput() { + List retVal = new ArrayList(); + StructureMap.StructureMapGroupInputComponent sourceIn = new StructureMap.StructureMapGroupInputComponent(); + StructureMap.StructureMapGroupInputComponent targetIn = new StructureMap.StructureMapGroupInputComponent(); + sourceIn.setName("source"); + sourceIn.setType("TestStructure"); + sourceIn.setMode(StructureMap.StructureMapInputMode.SOURCE); + targetIn.setName("target"); + targetIn.setType("http://hl7.org/fhir/StructureDefinition/Coding"); + targetIn.setMode(StructureMap.StructureMapInputMode.TARGET); + retVal.add(sourceIn); + retVal.add(targetIn); + return retVal; + } - public List buildTestRules() throws Exception { - List retVal = new ArrayList(); - StructureMap.StructureMapGroupRuleComponent codingSystem = new StructureMap.StructureMapGroupRuleComponent(); - StructureMap.StructureMapGroupRuleComponent codingExtension = new StructureMap.StructureMapGroupRuleComponent(); - codingSystem.setName("Coding.System"); - codingSystem.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[] { - this.buildSource("source", "system", "v", null, null, null) - })); - codingSystem.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[] { - this.buildTarget("target", "system", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("Id", "v") }) - })); - codingExtension.setName("Coding.Extension"); - codingExtension.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[] { - this.buildSource("source", "system", "v", null, null, null) - })); - codingExtension.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[] { - this.buildTarget("target", "extension", "ex", null, new TargetParam[] { new TargetParam("", "") }), - this.buildTarget("ex", "url", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("Id", "v") }), - this.buildTarget("ex", "value", null, StructureMap.StructureMapTransform.COPY, new TargetParam[] { new TargetParam("String", "v") }) - })); - retVal.add(codingSystem); - retVal.add(codingExtension); - return retVal; - } + public List buildTestRules() throws Exception { + List retVal = new ArrayList(); + StructureMap.StructureMapGroupRuleComponent codingSystem = new StructureMap.StructureMapGroupRuleComponent(); + StructureMap.StructureMapGroupRuleComponent codingExtension = new StructureMap.StructureMapGroupRuleComponent(); + codingSystem.setName("Coding.System"); + codingSystem.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[]{ + this.buildSource("source", "system", "v", null, null, null) + })); + codingSystem.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[]{ + this.buildTarget("target", "system", null, StructureMap.StructureMapTransform.COPY, new TargetParam[]{new TargetParam("Id", "v")}) + })); + codingExtension.setName("Coding.Extension"); + codingExtension.setSource(this.buildSourceList(new StructureMap.StructureMapGroupRuleSourceComponent[]{ + this.buildSource("source", "system", "v", null, null, null) + })); + codingExtension.setTarget(this.buildTargetList(new StructureMap.StructureMapGroupRuleTargetComponent[]{ + this.buildTarget("target", "extension", "ex", null, new TargetParam[]{new TargetParam("", "")}), + this.buildTarget("ex", "url", null, StructureMap.StructureMapTransform.COPY, new TargetParam[]{new TargetParam("Id", "v")}), + this.buildTarget("ex", "value", null, StructureMap.StructureMapTransform.COPY, new TargetParam[]{new TargetParam("String", "v")}) + })); + retVal.add(codingSystem); + retVal.add(codingExtension); + return retVal; + } - public List constructParameters(TargetParam[] params) throws Exception { - List parameterComponents = new ArrayList(); - for (TargetParam tp : params) { - if (tp.getType() == "Id") // TODO: Convert TypeParam.Type into an Enum. - parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IdType().setValue(tp.getValue()))); - else if (tp.getType() == "String") - parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue((new StringType().setValue(tp.getValue())))); - else if (tp.getType() == "Boolean") { - boolean bValue = Boolean.getBoolean(tp.getValue()); - parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new BooleanType().setValue(bValue))); - } else if (tp.getType() == "Integer") { - int iValue = Integer.getInteger(tp.getValue()); - parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IntegerType().setValue(iValue))); - } else if (tp.getType() == "Decimal") { - long lValue = Long.getLong(tp.getValue()); - parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new DecimalType(lValue))); - } - } - return parameterComponents; - } + public List constructParameters(TargetParam[] params) throws Exception { + List parameterComponents = new ArrayList(); + for (TargetParam tp : params) { + if (tp.getType() == "Id") // TODO: Convert TypeParam.Type into an Enum. + parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IdType().setValue(tp.getValue()))); + else if (tp.getType() == "String") + parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue((new StringType().setValue(tp.getValue())))); + else if (tp.getType() == "Boolean") { + boolean bValue = Boolean.getBoolean(tp.getValue()); + parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new BooleanType().setValue(bValue))); + } else if (tp.getType() == "Integer") { + int iValue = Integer.getInteger(tp.getValue()); + parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new IntegerType().setValue(iValue))); + } else if (tp.getType() == "Decimal") { + long lValue = Long.getLong(tp.getValue()); + parameterComponents.add(new StructureMap.StructureMapGroupRuleTargetParameterComponent().setValue(new DecimalType(lValue))); + } + } + return parameterComponents; + } - public List createMapStructureList() { - List retVal = new ArrayList(); - StructureMap.StructureMapStructureComponent source = new StructureMap.StructureMapStructureComponent(); - StructureMap.StructureMapStructureComponent target = new StructureMap.StructureMapStructureComponent(); - source.setUrl("http://opencimi.org/structuredefinition/TestStructure"); - source.setMode(StructureMap.StructureMapModelMode.SOURCE); - target.setUrl("http://hl7.org/fhir/StructureDefinition/Coding"); - target.setMode(StructureMap.StructureMapModelMode.TARGET); - retVal.add(source); - retVal.add(target); - return retVal; - } + public List createMapStructureList() { + List retVal = new ArrayList<>(); + StructureMap.StructureMapStructureComponent source = new StructureMap.StructureMapStructureComponent(); + StructureMap.StructureMapStructureComponent target = new StructureMap.StructureMapStructureComponent(); + source.setUrl("http://opencimi.org/structuredefinition/TestStructure"); + source.setMode(StructureMap.StructureMapModelMode.SOURCE); + target.setUrl("http://hl7.org/fhir/StructureDefinition/Coding"); + target.setMode(StructureMap.StructureMapModelMode.TARGET); + retVal.add(source); + retVal.add(target); + return retVal; + } - public StructureDefinition.StructureDefinitionDifferentialComponent createTestDiff() { - StructureDefinition.StructureDefinitionDifferentialComponent retVal = new StructureDefinition.StructureDefinitionDifferentialComponent(); - List eList = new ArrayList(); - ElementDefinition ed0 = new ElementDefinition(); - // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); - // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); - ed0.setId("TestStructure"); - ed0.setSliceName("TestStructure"); - ed0.setPath("TestStructure"); - // ed0.setBase(base); - ed0.setMin(1); - ed0.setMax("1"); - eList.add(ed0); + public StructureDefinition.StructureDefinitionDifferentialComponent createTestDiff() { + StructureDefinition.StructureDefinitionDifferentialComponent retVal = new StructureDefinition.StructureDefinitionDifferentialComponent(); + List eList = new ArrayList<>(); + ElementDefinition ed0 = new ElementDefinition(); + // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); + // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); + ed0.setId("TestStructure"); + ed0.setSliceName("TestStructure"); + ed0.setPath("TestStructure"); + // ed0.setBase(base); + ed0.setMin(1); + ed0.setMax("1"); + eList.add(ed0); - ElementDefinition ed = new ElementDefinition(); - // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); - // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); - ed.setId("system"); - ed.setSliceName("system"); - ed.setPath("TestStructure.system"); - // ed.setBase(base); - ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging")); - // ed.setType(this.createTypeRefList()); - eList.add(ed); - retVal.setElement(eList); - return retVal; - } + ElementDefinition ed = new ElementDefinition(); + // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); + // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); + ed.setId("system"); + ed.setSliceName("system"); + ed.setPath("TestStructure.system"); + // ed.setBase(base); + ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging")); + // ed.setType(this.createTypeRefList()); + eList.add(ed); + retVal.setElement(eList); + return retVal; + } - public StructureDefinition.StructureDefinitionSnapshotComponent createTestSnapshot() { - StructureDefinition.StructureDefinitionSnapshotComponent retVal = new StructureDefinition.StructureDefinitionSnapshotComponent(); - List eList = new ArrayList(); - ElementDefinition ed0 = new ElementDefinition(); - // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); - // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); - ed0.setId("TestStructure"); - ed0.setSliceName("TestStructure"); - ed0.setPath("TestStructure"); - // ed0.setBase(base); - ed0.setMin(1); - ed0.setMax("1"); - eList.add(ed0); + public StructureDefinition.StructureDefinitionSnapshotComponent createTestSnapshot() { + StructureDefinition.StructureDefinitionSnapshotComponent retVal = new StructureDefinition.StructureDefinitionSnapshotComponent(); + List eList = new ArrayList<>(); + ElementDefinition ed0 = new ElementDefinition(); + // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); + // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); + ed0.setId("TestStructure"); + ed0.setSliceName("TestStructure"); + ed0.setPath("TestStructure"); + // ed0.setBase(base); + ed0.setMin(1); + ed0.setMax("1"); + eList.add(ed0); - ElementDefinition ed = new ElementDefinition(); - // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); - // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); - ed.setId("system"); - ed.setSliceName("system"); - ed.setPath("TestStructure.system"); - // ed.setBase(base); - ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging")); - // ed.setType(this.createTypeRefList()); - ed.setMin(1); - ed.setMax("1"); - eList.add(ed); - retVal.setElement(eList); - return retVal; + ElementDefinition ed = new ElementDefinition(); + // ElementDefinition.ElementDefinitionBaseComponent base = new ElementDefinition.ElementDefinitionBaseComponent(); + // base.setId("http://hl7.org/fhir/StructureDefinition/Element"); + ed.setId("system"); + ed.setSliceName("system"); + ed.setPath("TestStructure.system"); + // ed.setBase(base); + ed.setFixed(new UriType().setValue("HTTP://opencimi.org/structuredefinition/TestStructure.html#Debugging")); + // ed.setType(this.createTypeRefList()); + ed.setMin(1); + ed.setMax("1"); + eList.add(ed); + retVal.setElement(eList); + return retVal; - } + } - public StructureDefinition createTestStructure() { - StructureDefinition sd = new StructureDefinition(); - sd.setId("TestStructure"); - sd.setUrl("http://opencimi.org/structuredefinition/TestStructure"); - sd.setStatus(Enumerations.PublicationStatus.DRAFT); - sd.setName("TestStructure"); - sd.setType("TestStructure"); - sd.setSnapshot(this.createTestSnapshot()); - sd.setDifferential(this.createTestDiff()); - sd.setKind(StructureDefinition.StructureDefinitionKind.LOGICAL); + public StructureDefinition createTestStructure() { + StructureDefinition sd = new StructureDefinition(); + sd.setId("TestStructure"); + sd.setUrl("http://opencimi.org/structuredefinition/TestStructure"); + sd.setStatus(Enumerations.PublicationStatus.DRAFT); + sd.setName("TestStructure"); + sd.setType("TestStructure"); + sd.setSnapshot(this.createTestSnapshot()); + sd.setDifferential(this.createTestDiff()); + sd.setKind(StructureDefinition.StructureDefinitionKind.LOGICAL); - return sd; - } + return sd; + } - public StructureMap createTestStructuremap() throws Exception { - StructureMap retMap = new StructureMap(); - retMap.setUrl("http://opencimi.org/structuremap/testtransform"); - retMap.setName("TestTransform"); - retMap.setStatus(Enumerations.PublicationStatus.DRAFT); - retMap.setStructure(this.createMapStructureList()); - retMap.setGroup(this.buildTestGroup()); - return retMap; - } + public StructureMap createTestStructuremap() throws Exception { + StructureMap retMap = new StructureMap(); + retMap.setUrl("http://opencimi.org/structuremap/testtransform"); + retMap.setName("TestTransform"); + retMap.setStatus(Enumerations.PublicationStatus.DRAFT); + retMap.setStructure(this.createMapStructureList()); + retMap.setGroup(this.buildTestGroup()); + return retMap; + } - /** - * Sets up the resource paths as well as create the contexts using a defalut validator to start with. - * - * @throws Exception - */ - @Before - public void setUp() throws Exception { - if (this.context == null) { - this.context = FhirContext.forDstu3(); - } - } + /** + * Sets up the resource paths as well as create the contexts using a defalut validator to start with. + */ + @Before + public void setUp() { + if (this.myCtx == null) { + this.myCtx = FhirContext.forDstu3(); + } + } - /** - * See #682 - */ - @Test - public void testMappingTransform() throws Exception { - Map maps = new HashMap(); // Instantiate a hashmap for StructureMaps - this.validationSupport = new PrePopulatedValidationSupport(); // Create Validation Instance - for (StructureDefinition sd : new DefaultProfileValidationSupport().fetchAllStructureDefinitions(this.context)) { // Read in the default Structure Definitions into a validator that allows custom - // declared structure definitions. - this.validationSupport.addStructureDefinition(sd); - } - StructureDefinition sd1 = this.createTestStructure(); // Calls a method that constructs a comp - this.validationSupport.addStructureDefinition(sd1); // Add custom structure to validation support. - this.hapiContext = new HapiWorkerContext(this.context, this.validationSupport);// Init the Hapi Work - StructureMap map = this.createTestStructuremap(); - maps.put(map.getUrl(), map); - StructureMapUtilities scu = new StructureMapUtilities(hapiContext, maps, null, null); - List result = scu.analyse(null, map).getProfiles(); + /** + * See #682 + */ + @Test + public void testMappingTransform() throws Exception { + Map maps = new HashMap<>(); // Instantiate a hashmap for StructureMaps + PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(myCtx); + this.validationSupport = new ValidationSupportChain(prePopulatedValidationSupport, new DefaultProfileValidationSupport(myCtx)); - assertEquals(1, result.size()); + StructureDefinition sd1 = this.createTestStructure(); // Calls a method that constructs a comp + prePopulatedValidationSupport.addStructureDefinition(sd1); // Add custom structure to validation support. + this.hapiContext = new HapiWorkerContext(this.myCtx, this.validationSupport);// Init the Hapi Work + StructureMap map = this.createTestStructuremap(); + maps.put(map.getUrl(), map); + StructureMapUtilities scu = new StructureMapUtilities(hapiContext, maps, null, null); + List result = scu.analyse(null, map).getProfiles(); - ourLog.info(context.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.get(0))); - } + assertEquals(1, result.size()); - public class TargetParam { - private String type; + ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.get(0))); + } - private String value; + public class TargetParam { + private String type; - public TargetParam(String type, String value) { + private String value; - this.type = type; - this.value = value; - } + public TargetParam(String type, String value) { - public String getType() { - return type; - } + this.type = type; + this.value = value; + } - public String getValue() { - return value; - } + public String getType() { + return type; + } - public void setType(String type) { - this.type = type; - } + public void setType(String type) { + this.type = type; + } - public void setValue(String value) { - this.value = value; - } - } + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/utils/FhirPathEngineTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/utils/FhirPathEngineTest.java index 2d9fdd7527b..68c7084dc90 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/utils/FhirPathEngineTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/utils/FhirPathEngineTest.java @@ -2,9 +2,16 @@ package org.hl7.fhir.dstu3.utils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Base; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Specimen; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.exceptions.FHIRException; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -12,7 +19,9 @@ import org.junit.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class FhirPathEngineTest { @@ -21,7 +30,7 @@ public class FhirPathEngineTest { private static FHIRPathEngine ourEngine; @Test - public void testAs() throws Exception { + public void testAs() { Observation obs = new Observation(); obs.setValue(new StringType("FOO")); @@ -66,13 +75,13 @@ public class FhirPathEngineTest { } @AfterClass - public static void afterClassClearContext() throws Exception { + public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } @BeforeClass public static void beforeClass() { - ourEngine = new FHIRPathEngine(new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport())); + ourEngine = new FHIRPathEngine(new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport(ourCtx))); } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContextTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContextTest.java deleted file mode 100644 index 945cd6698e9..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/instance/hapi/validation/HapiWorkerContextTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.hl7.fhir.instance.hapi.validation; - -import ca.uhn.fhir.context.FhirContext; -import org.hl7.fhir.dstu2.model.ValueSet; -import org.hl7.fhir.dstu2.utils.IWorkerContext; -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class HapiWorkerContextTest { - - @Test - public void testIdTypes(){ - - DefaultProfileValidationSupport validationSupport = new DefaultProfileValidationSupport(); - FhirContext ctx = FhirContext.forDstu2(); - HapiWorkerContext hwc = new HapiWorkerContext(ctx, validationSupport); - - ValueSet vs = validationSupport.fetchResource(ctx, ValueSet.class, "http://hl7.org/fhir/ValueSet/defined-types"); - IWorkerContext.ValidationResult outcome; - - outcome = hwc.validateCode("http://hl7.org/fhir/resource-types", "Patient", null); - assertTrue(outcome.isOk()); - - outcome = hwc.validateCode("http://hl7.org/fhir/resource-types", "Patient", null, vs); - assertTrue(outcome.isOk()); - - outcome = hwc.validateCode(null, "Patient", null, vs); - assertTrue(outcome.isOk()); - - outcome = hwc.validateCode(null, "id", null, vs); - assertTrue(outcome.isOk()); - - outcome = hwc.validateCode(null, "foo", null, vs); - assertFalse(outcome.isOk()); - - } - - -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java index acde719ed9a..9e7256803d6 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.utils.FhirPathEngineTest; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; @@ -164,7 +164,7 @@ public class FhirPathEngineR4Test { @BeforeClass public static void beforeClass() { - ourEngine = new FHIRPathEngine(new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport())); + ourEngine = new FHIRPathEngine(new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport(ourCtx))); } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/CustomResourceGenerationTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/CustomResourceGenerationTest.java index 6e2d2cee751..99b48c10d8e 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/CustomResourceGenerationTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/CustomResourceGenerationTest.java @@ -4,12 +4,11 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.StructureDefinition; -import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,10 +28,10 @@ public class CustomResourceGenerationTest extends BaseTest { StructureDefinition customProfile = loadResource(myCtx, StructureDefinition.class, "/r4/custom-resource-profile.json"); String customResource = loadResource("/r4/custom-resource.json"); - PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(); + PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(myCtx); prePopulatedValidationSupport.addStructureDefinition(customProfile); - DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(); + DefaultProfileValidationSupport defaultProfileValidationSupport = new DefaultProfileValidationSupport(myCtx); ValidationSupportChain validationSupport = new ValidationSupportChain(defaultProfileValidationSupport, prePopulatedValidationSupport); FhirValidator validator = myCtx.newValidator(); @@ -43,9 +42,10 @@ public class CustomResourceGenerationTest extends BaseTest { String outcome = myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()); ourLog.info(outcome); - assertEquals(2, result.getMessages().size()); + assertEquals(3, result.getMessages().size()); assertEquals("Error parsing JSON: the primitive value must be a boolean", result.getMessages().get(0).getMessage()); - assertEquals("Unrecognised property '@id1'", result.getMessages().get(1).getMessage()); + assertEquals("This property must be an Array, not a a primitive property", result.getMessages().get(1).getMessage()); + assertEquals("Unrecognised property '@id1'", result.getMessages().get(2).getMessage()); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index fe617e9b955..f9aa04ae046 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -1,7 +1,10 @@ package org.hl7.fhir.r4.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.util.TestUtil; @@ -12,23 +15,21 @@ import ca.uhn.fhir.validation.ValidationResult; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander; import org.hl7.fhir.r4.utils.FHIRPathEngine; @@ -66,16 +67,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class FhirInstanceValidatorR4Test extends BaseTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorR4Test.class); - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forR4(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); @Rule public TestRule watcher = new TestWatcher() { @Override @@ -84,11 +88,11 @@ public class FhirInstanceValidatorR4Test extends BaseTest { } }; private FhirInstanceValidator myInstanceVal; - private IValidationSupport myMockSupport; private Map mySupportedCodeSystemsForExpansion; private FhirValidator myVal; private ArrayList myValidConcepts; private Set myValidSystems = new HashSet<>(); + private CachingValidationSupport myValidationSupport; private void addValidConcept(String theSystem, String theCode) { myValidSystems.add(theSystem); @@ -118,9 +122,12 @@ public class FhirInstanceValidatorR4Test extends BaseTest { myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - myMockSupport = mock(IValidationSupport.class); - CachingValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(myDefaultValidationSupport, myMockSupport)); - myInstanceVal = new FhirInstanceValidator(validationSupport); + IValidationSupport mockSupport = mock(IValidationSupport.class); + when(mockSupport.getFhirContext()).thenReturn(ourCtx); + + ValidationSupportChain chain = new ValidationSupportChain(myDefaultValidationSupport, mockSupport, new InMemoryTerminologyServerValidationSupport(ourCtx), new CommonCodeSystemsTerminologyService(ourCtx)); + myValidationSupport = new CachingValidationSupport(chain); + myInstanceVal = new FhirInstanceValidator(myValidationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -128,11 +135,11 @@ public class FhirInstanceValidatorR4Test extends BaseTest { myValidConcepts = new ArrayList<>(); - when(myMockSupport.expandValueSet(nullable(FhirContext.class), nullable(ConceptSetComponent.class))).thenAnswer(t -> { - ConceptSetComponent arg = (ConceptSetComponent) t.getArguments()[1]; - ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getSystem()); + when(mockSupport.expandValueSet(any(), nullable(ValueSetExpansionOptions.class), any())).thenAnswer(t -> { + ValueSet arg = (ValueSet) t.getArgument(2, IBaseResource.class); + ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getCompose().getIncludeFirstRep().getSystem()); if (retVal == null) { - ValueSetExpander.ValueSetExpansionOutcome outcome = myDefaultValidationSupport.expandValueSet(ourCtx, arg); + IBaseResource outcome = myDefaultValidationSupport.expandValueSet(myDefaultValidationSupport, null, arg).getValueSet(); return outcome; } ourLog.debug("expandValueSet({}) : {}", new Object[]{t.getArguments()[0], retVal}); @@ -141,66 +148,70 @@ public class FhirInstanceValidatorR4Test extends BaseTest { valueset.setExpansion(retVal); return new ValueSetExpander.ValueSetExpansionOutcome(valueset); }); - when(myMockSupport.isCodeSystemSupported(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.isCodeSystemSupported(any(), nullable(String.class))).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock theInvocation) { - boolean retVal = myValidSystems.contains(theInvocation.getArgument(1, String.class)); - ourLog.debug("isCodeSystemSupported({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal}); + String argument = theInvocation.getArgument(1, String.class); + boolean retVal = myValidSystems.contains(argument); + ourLog.debug("isCodeSystemSupported({}) : {}", argument, retVal); return retVal; } }); - when(myMockSupport.fetchResource(nullable(FhirContext.class), nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchResource(nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { @Override public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable { IBaseResource retVal; - String id = (String) theInvocation.getArguments()[2]; + Class clazz = (Class) theInvocation.getArguments()[0]; + String id = theInvocation.getArgument(1, String.class); if ("Questionnaire/q_jon".equals(id)) { retVal = ourCtx.newJsonParser().parseResource(loadResource("/q_jon.json")); } else { - retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class) theInvocation.getArguments()[1], id); + retVal = myDefaultValidationSupport.fetchResource(clazz, id); } - ourLog.debug("fetchResource({}, {}) : {}", theInvocation.getArguments()[1], id, retVal); + ourLog.debug("fetchResource({}, {}) : {}", clazz, id, retVal); return retVal; } }); - when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.validateCode(any(), any(), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { @Override - public IContextValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { - FhirContext ctx = theInvocation.getArgument(0, FhirContext.class); - String system = theInvocation.getArgument(1, String.class); - String code = theInvocation.getArgument(2, String.class); - String display = theInvocation.getArgument(3, String.class); - String valueSetUrl = theInvocation.getArgument(4, String.class); - IContextValidationSupport.CodeValidationResult retVal; + public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { + ConceptValidationOptions options = theInvocation.getArgument(1, ConceptValidationOptions.class); + String system = theInvocation.getArgument(2, String.class); + String code = theInvocation.getArgument(3, String.class); + String display = theInvocation.getArgument(4, String.class); + String valueSetUrl = theInvocation.getArgument(5, String.class); + IValidationSupport.CodeValidationResult retVal; if (myValidConcepts.contains(system + "___" + code)) { - retVal = new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code))); + retVal = new IValidationSupport.CodeValidationResult().setCode(code); + } else if (myValidSystems.contains(system)) { + return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.WARNING.toCode()).setMessage("Unknown code: " + system + " / " + code); } else { - retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl); + retVal = myDefaultValidationSupport.validateCode(myDefaultValidationSupport, options, system, code, display, valueSetUrl); } ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal); return retVal; } }); - when(myMockSupport.fetchCodeSystem(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchCodeSystem(nullable(String.class))).thenAnswer(new Answer() { @Override public CodeSystem answer(InvocationOnMock theInvocation) { - CodeSystem retVal = myDefaultValidationSupport.fetchCodeSystem((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]); - ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal}); + CodeSystem retVal = (CodeSystem) myDefaultValidationSupport.fetchCodeSystem((String) theInvocation.getArguments()[0]); + ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal}); return retVal; } }); - when(myMockSupport.fetchStructureDefinition(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(mockSupport.fetchStructureDefinition(nullable(String.class))).thenAnswer(new Answer() { @Override - public StructureDefinition answer(InvocationOnMock theInvocation) { - StructureDefinition retVal = myDefaultValidationSupport.fetchStructureDefinition((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]); - ourLog.debug("fetchStructureDefinition({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal}); + public IBaseResource answer(InvocationOnMock theInvocation) { + IBaseResource retVal = myDefaultValidationSupport.fetchStructureDefinition((String) theInvocation.getArguments()[0]); + ourLog.debug("fetchStructureDefinition({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal}); return retVal; } }); - when(myMockSupport.fetchAllStructureDefinitions(nullable(FhirContext.class))).thenAnswer(new Answer>() { + when(mockSupport.fetchAllStructureDefinitions()).thenAnswer(new Answer>() { @Override public List answer(InvocationOnMock theInvocation) { - List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions((FhirContext) theInvocation.getArguments()[0]); + List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions(); ourLog.debug("fetchAllStructureDefinitions()", new Object[]{}); return retVal; } @@ -214,7 +225,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { private StructureDefinition loadStructureDefinition(DefaultProfileValidationSupport theDefaultValSupport, String theResName) throws IOException, FHIRException { StructureDefinition derived = loadResource(ourCtx, StructureDefinition.class, theResName); - StructureDefinition base = theDefaultValSupport.fetchStructureDefinition(ourCtx, derived.getBaseDefinition()); + StructureDefinition base = (StructureDefinition) theDefaultValSupport.fetchStructureDefinition(derived.getBaseDefinition()); Validate.notNull(base); IWorkerContext worker = new HapiWorkerContext(ourCtx, theDefaultValSupport); @@ -249,12 +260,12 @@ public class FhirInstanceValidatorR4Test extends BaseTest { .setCode("AA "); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(p); List all = logResultsAndReturnErrorOnes(result); assertFalse(result.isSuccessful()); - assertEquals("The code 'AA ' is not valid (whitespace rules)", all.get(0).getMessage()); + assertEquals("The code \"AA \" is not valid (whitespace rules)", all.get(0).getMessage()); } @@ -272,12 +283,38 @@ public class FhirInstanceValidatorR4Test extends BaseTest { ""; FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(input); List all = logResultsAndReturnAll(result); assertFalse(result.isSuccessful()); - assertEquals("Primitive types must have a value that is not empty", all.get(0).getMessage()); + assertEquals("@value cannot be empty", all.get(0).getMessage()); + } + + /** + * See #1740 + */ + @Test + public void testValidateScalarInRepeatableField() { + String operationDefinition = "{\n" + + " \"resourceType\": \"OperationDefinition\",\n" + + " \"name\": \"Questionnaire\",\n" + + " \"status\": \"draft\",\n" + + " \"kind\" : \"operation\",\n" + + " \"code\": \"populate\",\n" + + " \"resource\": \"Patient\",\n" + // should be array + " \"system\": false,\n" + " " + + " \"type\": false,\n" + + " \"instance\": true\n" + + "}"; + + FhirValidator val = ourCtx.newValidator(); + val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + + ValidationResult result = val.validateWithResult(operationDefinition); + List all = logResultsAndReturnAll(result); + assertFalse(result.isSuccessful()); + assertEquals("This property must be an Array, not a a primitive property", all.get(0).getMessage()); } /** @@ -294,7 +331,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { ""; FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(input); logResultsAndReturnAll(result); @@ -318,7 +355,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { // With BPs disabled val = ourCtx.newValidator(); - instanceModule = new FhirInstanceValidator(myDefaultValidationSupport); + instanceModule = new FhirInstanceValidator(myValidationSupport); val.registerValidatorModule(instanceModule); result = val.validateWithResult(input); all = logResultsAndReturnAll(result); @@ -327,7 +364,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { // With BPs enabled val = ourCtx.newValidator(); - instanceModule = new FhirInstanceValidator(myDefaultValidationSupport); + instanceModule = new FhirInstanceValidator(myValidationSupport); IResourceValidator.BestPracticeWarningLevel level = IResourceValidator.BestPracticeWarningLevel.Error; instanceModule.setBestPracticeWarningLevel(level); val.registerValidatorModule(instanceModule); @@ -400,7 +437,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { List errors = logResultsAndReturnNonInformationalOnes(output); errors = errors .stream() - .filter(t->t.getMessage().contains("Bundle entry missing fullUrl")) + .filter(t -> t.getMessage().contains("Bundle entry missing fullUrl")) .collect(Collectors.toList()); assertEquals(5, errors.size()); } @@ -441,7 +478,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { procedure.setPerformed(period); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(myInstanceVal); ValidationResult result = val.validateWithResult(procedure); @@ -601,7 +638,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { inputString = loadResource("/brian_reinhold_bundle.json"); Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, inputString); - FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myDefaultValidationSupport)); + FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myValidationSupport)); List fpOutput; BooleanType bool; @@ -633,9 +670,9 @@ public class FhirInstanceValidatorR4Test extends BaseTest { @Test public void testValidateProfileWithExtension() throws IOException, FHIRException { - PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport(); - DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); - CachingValidationSupport support = new CachingValidationSupport(new ValidationSupportChain(defaultSupport, valSupport)); + PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport(ourCtx); + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ourCtx); + CachingValidationSupport support = new CachingValidationSupport(new ValidationSupportChain(defaultSupport, valSupport, new InMemoryTerminologyServerValidationSupport(ourCtx))); // Prepopulate SDs valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/r4/myconsent-profile.xml")); @@ -658,7 +695,6 @@ public class FhirInstanceValidatorR4Test extends BaseTest { .setCode("acd"); - // Should pass ValidationResult output = val.validateWithResult(input); List all = logResultsAndReturnErrorOnes(output); @@ -897,29 +933,6 @@ public class FhirInstanceValidatorR4Test extends BaseTest { assertEquals(output.toString(), 0, res.size()); } - @Test - public void testValidateRawXmlWithMissingRootNamespace() { - String input = "" - + "" - + " " - + " " - + "
        Some narrative
        " - + "
        " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + "
        "; - - ValidationResult output = myVal.validateWithResult(input); - assertEquals(output.toString(), 1, output.getMessages().size()); - assertEquals("This 'Patient' cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); - ourLog.info(output.getMessages().get(0).getLocationString()); - } - /** * A reference with only an identifier should be valid */ @@ -993,7 +1006,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnAll(output); @@ -1014,7 +1027,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); @@ -1035,10 +1048,13 @@ public class FhirInstanceValidatorR4Test extends BaseTest { input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); input.setStatus(ObservationStatus.FINAL); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); - assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); + + assertEquals(1, errors.size()); + assertEquals("Profile reference \"http://foo/structuredefinition/myprofile\" could not be resolved, so has not been checked", errors.get(0).getMessage()); + assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity()); } @Test @@ -1086,7 +1102,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { ValidationResult output = myVal.validateWithResult(input); logResultsAndReturnAll(output); assertEquals( - "The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.0.0 (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code[notvalidcode] in system[(none)])", + "The value provided (\"notvalidcode\") is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.0.0 (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code 'notvalidcode')", output.getMessages().get(0).getMessage()); } @@ -1094,7 +1110,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { @Ignore public void testValidateDecimalWithTrailingDot() { String input = "{" + - " \"resourceType\": \"Observation\"," + + " \"resourceType\": \"Observation\"," + " \"status\": \"final\"," + " \"subject\": {\"reference\":\"Patient/123\"}," + " \"code\": { \"coding\": [{ \"system\":\"http://foo\", \"code\":\"123\" }] }," + @@ -1110,8 +1126,8 @@ public class FhirInstanceValidatorR4Test extends BaseTest { " },\n" + " \"text\": \"210.0-925.\"\n" + " }\n" + - " ]"+ - "}"; + " ]" + + "}"; ourLog.info(input); ValidationResult output = myVal.validateWithResult(input); logResultsAndReturnAll(output); @@ -1125,7 +1141,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); input.setStatus(ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); @@ -1141,7 +1157,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1159,7 +1175,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1179,7 +1195,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { expansionComponent.addContains().setSystem("http://loinc.org").setCode("12345").setDisplay("Some display code"); mySupportedCodeSystemsForExpansion.put("http://loinc.org", expansionComponent); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1196,7 +1212,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(ObservationStatus.FINAL); @@ -1263,7 +1279,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { String encoded = loadResource("/r4/r4-caredove-bundle.json"); IResourceValidator.IValidatorResourceFetcher resourceFetcher = mock(IResourceValidator.IValidatorResourceFetcher.class); - when(resourceFetcher.validationPolicy(any(),anyString(), anyString())).thenReturn(IResourceValidator.ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); + when(resourceFetcher.validationPolicy(any(), anyString(), anyString())).thenReturn(IResourceValidator.ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); myInstanceVal.setValidatorResourceFetcher(resourceFetcher); myVal.validateWithResult(encoded); @@ -1285,6 +1301,81 @@ public class FhirInstanceValidatorR4Test extends BaseTest { ourLog.info(output.getMessages().get(0).getMessage()); } + @Test + public void testValidateCurrency() { + String input = "{\n" + + " \"resourceType\": \"Invoice\",\n" + + " \"status\": \"draft\",\n" + + " \"date\": \"2020-01-08\",\n" + + " \"totalGross\": {\n" + + " \"value\": 150,\n" + + " \"currency\": \"USD\"\n" + + " }\n" + + "}"; + ValidationResult output = myVal.validateWithResult(input); + List errors = logResultsAndReturnNonInformationalOnes(output); + assertEquals(errors.toString(), 0, errors.size()); + + + } + + @Test + public void testValidateCurrency_Wrong() { + String input = "{\n" + + " \"resourceType\": \"Invoice\",\n" + + " \"status\": \"draft\",\n" + + " \"date\": \"2020-01-08\",\n" + + " \"totalGross\": {\n" + + " \"value\": 150,\n" + + " \"currency\": \"BLAH\"\n" + + " }\n" + + "}"; + ValidationResult output = myVal.validateWithResult(input); + List errors = logResultsAndReturnNonInformationalOnes(output); + assertEquals(errors.toString(), 1, errors.size()); + assertThat(errors.get(0).getMessage(), containsString("The value provided (\"BLAH\") is not in the value set http://hl7.org/fhir/ValueSet/currencies")); + + + } + + @Test + public void testValidateReferenceTargetType_Correct() { + + AllergyIntolerance allergy = new AllergyIntolerance(); + allergy.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical").setCode("active").setDisplay("Active"); + allergy.getVerificationStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-verification").setCode("confirmed").setDisplay("Confirmed"); + allergy.setPatient(new Reference("Patient/123")); + + allergy.addNote() + .setText("This is text") + .setAuthor(new Reference("Patient/123")); + + ValidationResult output = myVal.validateWithResult(allergy); + List errors = logResultsAndReturnNonInformationalOnes(output); + assertEquals(errors.toString(), 0, errors.size()); + + } + + @Test + @Ignore + public void testValidateReferenceTargetType_Incorrect() { + + AllergyIntolerance allergy = new AllergyIntolerance(); + allergy.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical").setCode("active").setDisplay("Active"); + allergy.getVerificationStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-verification").setCode("confirmed").setDisplay("Confirmed"); + allergy.setPatient(new Reference("Patient/123")); + + allergy.addNote() + .setText("This is text") + .setAuthor(new Reference("CodeSystems/123")); + + ValidationResult output = myVal.validateWithResult(allergy); + List errors = logResultsAndReturnNonInformationalOnes(output); + assertEquals(errors.toString(), 0, errors.size()); + assertThat(errors.get(0).getMessage(), containsString("The value provided (\"BLAH\") is not in the value set http://hl7.org/fhir/ValueSet/currencies")); + + } + @AfterClass public static void afterClassClearContext() { myDefaultValidationSupport.flush(); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/HapiWorkerContextTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/HapiWorkerContextTest.java index fd3dbf780dc..40ee45ce4cb 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/HapiWorkerContextTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/HapiWorkerContextTest.java @@ -5,17 +5,15 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.test.BaseTest; import com.google.common.base.Charsets; import org.apache.commons.lang.Validate; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.PrePopulatedValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.utilities.TerminologyServiceOptions; +import org.hl7.fhir.utilities.validation.ValidationOptions; import org.junit.Test; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -32,25 +30,26 @@ import java.util.List; import static org.junit.Assert.assertEquals; public class HapiWorkerContextTest extends BaseTest { - FhirContext ctx = FhirContext.forR4(); + FhirContext myCtx = FhirContext.forR4(); @Test public void testCodeInPrePopulatedValidationSupport() throws IOException { - PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(); + PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(myCtx); - getResources("/r4/carin/carin/codesystem/").forEach(t -> prePopulatedValidationSupport.addCodeSystem((CodeSystem) t)); - getResources("/r4/carin/uscore/codesystem/").forEach(t -> prePopulatedValidationSupport.addCodeSystem((CodeSystem) t)); + getResources("/r4/carin/carin/codesystem/").forEach(t -> prePopulatedValidationSupport.addCodeSystem(t)); + getResources("/r4/carin/uscore/codesystem/").forEach(t -> prePopulatedValidationSupport.addCodeSystem(t)); getResources("/r4/carin/carin/valueset/").forEach(t -> prePopulatedValidationSupport.addValueSet((ValueSet) t)); getResources("/r4/carin/uscore/valueset/").forEach(t -> prePopulatedValidationSupport.addValueSet((ValueSet) t)); - getResources("/r4/carin/carin/structuredefinition/").forEach(t -> prePopulatedValidationSupport.addStructureDefinition((StructureDefinition) t)); - getResources("/r4/carin/uscore/structuredefinition/").forEach(t -> prePopulatedValidationSupport.addStructureDefinition((StructureDefinition) t)); + getResources("/r4/carin/carin/structuredefinition/").forEach(t -> prePopulatedValidationSupport.addStructureDefinition(t)); + getResources("/r4/carin/uscore/structuredefinition/").forEach(t -> prePopulatedValidationSupport.addStructureDefinition(t)); - IValidationSupport validationSupportChain = new ValidationSupportChain( - new DefaultProfileValidationSupport(), - prePopulatedValidationSupport + ValidationSupportChain validationSupportChain = new ValidationSupportChain( + new DefaultProfileValidationSupport(myCtx), + prePopulatedValidationSupport, + new InMemoryTerminologyServerValidationSupport(myCtx) ); - HapiWorkerContext workerCtx = new HapiWorkerContext(ctx, validationSupportChain); + HapiWorkerContext workerCtx = new HapiWorkerContext(myCtx, validationSupportChain); ValueSet vs = new ValueSet(); IWorkerContext.ValidationResult outcome; @@ -58,20 +57,21 @@ public class HapiWorkerContextTest extends BaseTest { // Built-in Codes vs.setUrl("http://hl7.org/fhir/ValueSet/fm-status"); - outcome = workerCtx.validateCode(new TerminologyServiceOptions(), "active", vs); + ValidationOptions options = new ValidationOptions().guessSystem(); + outcome = workerCtx.validateCode(options, "active", vs); assertEquals(outcome.getMessage(), true, outcome.isOk()); - outcome = workerCtx.validateCode(new TerminologyServiceOptions(), "active2", vs); + outcome = workerCtx.validateCode(options, "active2", vs); assertEquals(outcome.getMessage(), false, outcome.isOk()); assertEquals("Unknown code[active2] in system[(none)]", outcome.getMessage()); // PrePopulated codes vs.setUrl("http://hl7.org/fhir/us/core/ValueSet/birthsex"); - outcome = workerCtx.validateCode(new TerminologyServiceOptions(), "F", vs); + outcome = workerCtx.validateCode(options, "F", vs); assertEquals(outcome.getMessage(), true, outcome.isOk()); - outcome = workerCtx.validateCode(new TerminologyServiceOptions(), "F2", vs); + outcome = workerCtx.validateCode(options, "F2", vs); assertEquals(outcome.getMessage(), false, outcome.isOk()); assertEquals("Unknown code[F2] in system[(none)]", outcome.getMessage()); @@ -92,7 +92,7 @@ public class HapiWorkerContextTest extends BaseTest { for (Resource nextFileResource : resources) { try (InputStream is = nextFileResource.getInputStream()) { Reader reader = new InputStreamReader(is, Charsets.UTF_8); - retVal.add(ctx.newJsonParser().parseResource(reader)); + retVal.add(myCtx.newJsonParser().parseResource(reader)); } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireResponseValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireResponseValidatorR4Test.java index 0c078452dc6..2d8d8815c24 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireResponseValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireResponseValidatorR4Test.java @@ -1,26 +1,23 @@ package org.hl7.fhir.r4.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.AfterClass; import org.junit.Before; @@ -31,11 +28,15 @@ import java.util.Arrays; import java.util.Date; import java.util.List; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,24 +46,22 @@ public class QuestionnaireResponseValidatorR4Test { private static final String CODE_ICC_SCHOOLTYPE_PT = "PT"; private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype"; private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype"; - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forR4(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); private FhirInstanceValidator myInstanceVal; private FhirValidator myVal; private IValidationSupport myValSupport; - private HapiWorkerContext myWorkerCtx; @Before public void before() { myValSupport = mock(IValidationSupport.class); - // new DefaultProfileValidationSupport(); - myWorkerCtx = new HapiWorkerContext(ourCtx, myValSupport); + when(myValSupport.getFhirContext()).thenReturn(ourCtx); myVal = ourCtx.newValidator(); myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport); + ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport, new InMemoryTerminologyServerValidationSupport(ourCtx)); myInstanceVal = new FhirInstanceValidator(validationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -87,11 +86,11 @@ public class QuestionnaireResponseValidatorR4Test { codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); int itemCnt = 16; QuestionnaireItemType[] questionnaireItemTypes = new QuestionnaireItemType[itemCnt]; @@ -152,12 +151,12 @@ public class QuestionnaireResponseValidatorR4Test { qa.setQuestionnaire("http://example.com/Questionnaire/q" + i); qa.addItem().setLinkId(linkId).addAnswer().setValue(answerValues[i]); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); - when(myValSupport.validateCode(any(), any(), any(), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); - when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); + when(myValSupport.validateCode(any(), any(), any(), any(), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); + when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); ValidationResult errors = myVal.validateWithResult(qa); @@ -178,7 +177,7 @@ public class QuestionnaireResponseValidatorR4Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaireElement().getValue()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaireElement().getValue()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); @@ -192,35 +191,35 @@ public class QuestionnaireResponseValidatorR4Test { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setAnswerValueSet("http://somevalueset"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult(new CodeSystem.ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) - .thenReturn(new IValidationSupport.CodeValidationResult(new CodeSystem.ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) - .thenReturn(new IValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); QuestionnaireResponse qa; ValidationResult errors; @@ -246,7 +245,7 @@ public class QuestionnaireResponseValidatorR4Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)")); + assertThat(errors.toString(), containsString("Unknown code for \"http://codesystems.com/system#code1\"")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); qa = new QuestionnaireResponse(); @@ -257,7 +256,7 @@ public class QuestionnaireResponseValidatorR4Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3")); + assertThat(errors.toString(), containsString("Unknown code 'http://codesystems.com/system2#code3' for \"http://codesystems.com/system2#code3\"")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); } @@ -274,7 +273,7 @@ public class QuestionnaireResponseValidatorR4Test { QuestionnaireResponseItemComponent qaGroup = qa.addItem(); qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -294,7 +293,7 @@ public class QuestionnaireResponseValidatorR4Test { QuestionnaireResponseItemComponent qaItem = qa.addItem().setLinkId("link0"); qaItem.addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -315,7 +314,7 @@ public class QuestionnaireResponseValidatorR4Test { qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); String reference = qa.getQuestionnaire(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -340,22 +339,22 @@ public class QuestionnaireResponseValidatorR4Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))) .thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue)))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -397,22 +396,22 @@ public class QuestionnaireResponseValidatorR4Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))) .thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue)))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -450,7 +449,7 @@ public class QuestionnaireResponseValidatorR4Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); @@ -483,7 +482,7 @@ public class QuestionnaireResponseValidatorR4Test { .setRequired(true); String reference = "http://example.com/Questionnaire/q1"; - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))) .thenReturn(q); QuestionnaireResponse qa = new QuestionnaireResponse(); @@ -512,28 +511,28 @@ public class QuestionnaireResponseValidatorR4Test { Questionnaire q = new Questionnaire(); QuestionnaireItemComponent item = q.addItem(); item.setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.OPENCHOICE).setAnswerValueSet("http://somevalueset"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setCode("Unknown code")); QuestionnaireResponse qa; ValidationResult errors; @@ -617,6 +616,7 @@ public class QuestionnaireResponseValidatorR4Test { qa.addItem().setLinkId("link0").addAnswer().setValue(new IntegerType(123)); errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); + assertThat(errors.toString(), containsString("Cannot validate integer answer option because no option list is provided")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); @@ -659,7 +659,7 @@ public class QuestionnaireResponseValidatorR4Test { .setRequired(true) .setType(QuestionnaireItemType.OPENCHOICE) .setAnswerOption(options); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); QuestionnaireResponse qa; ValidationResult errors; @@ -705,7 +705,7 @@ public class QuestionnaireResponseValidatorR4Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -723,7 +723,7 @@ public class QuestionnaireResponseValidatorR4Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link1").addItem().setLinkId("link2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -782,9 +782,9 @@ public class QuestionnaireResponseValidatorR4Test { .addAnswer() .setValue(new Coding(SYSTEMURI_ICC_SCHOOLTYPE, CODE_ICC_SCHOOLTYPE_PT, "")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(questionnaire); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE))).thenReturn(iccSchoolTypeVs); - when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(ValueSet.class) )).thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(new CodeType(CODE_ICC_SCHOOLTYPE_PT)))); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(questionnaire); + when(myValSupport.fetchResource(eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE))).thenReturn(iccSchoolTypeVs); + when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any(ValueSet.class))).thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java index ff872af31f4..a184502f615 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java @@ -1,15 +1,16 @@ package org.hl7.fhir.r4.validation; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; import org.hamcrest.Matchers; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; @@ -28,23 +29,25 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class QuestionnaireValidatorR4Test { private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorR4Test.class); - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forR4(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); private FhirInstanceValidator myInstanceVal; private FhirValidator myVal; @Before public void before() { IValidationSupport myValSupport = mock(IValidationSupport.class); + when(myValSupport.getFhirContext()).thenReturn(ourCtx); myVal = ourCtx.newValidator(); myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport); + ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport, new InMemoryTerminologyServerValidationSupport(ourCtx)); myInstanceVal = new FhirInstanceValidator(validationSupport); myVal.registerValidatorModule(myInstanceVal); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SnapshotGeneratorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SnapshotGeneratorR4Test.java index 4af1bb66bbc..6951a9172e1 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SnapshotGeneratorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SnapshotGeneratorR4Test.java @@ -4,10 +4,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.EncodingEnum; import org.apache.commons.io.IOUtils; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.validation.SnapshotGeneratingValidationSupport; -import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r4.model.StructureDefinition; import org.junit.Test; import org.slf4j.Logger; @@ -31,12 +31,12 @@ public class SnapshotGeneratorR4Test { // Create a validation chain that includes default validation support and a // snapshot generator - DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); - SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx, defaultSupport); + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(myFhirCtx); + SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx); ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapshotGenerator); // Generate the snapshot - StructureDefinition snapshot = chain.generateSnapshot(differential, "http://foo", null, "THE BEST PROFILE"); + StructureDefinition snapshot = (StructureDefinition) chain.generateSnapshot(chain, differential, "http://foo", null, "THE BEST PROFILE"); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(snapshot)); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java index 05a8e54e3ff..df5b855c627 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java @@ -1,7 +1,10 @@ package org.hl7.fhir.r5.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; @@ -10,18 +13,17 @@ import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.hapi.validation.CachingValidationSupport; -import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r5.model.*; -import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.terminologies.ValueSetExpander; import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.AfterClass; import org.junit.Before; @@ -53,16 +55,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class FhirInstanceValidatorR5Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorR5Test.class); - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forR5(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); @Rule public TestRule watcher = new TestWatcher() { @Override @@ -76,6 +81,7 @@ public class FhirInstanceValidatorR5Test { private FhirValidator myVal; private ArrayList myValidConcepts; private Set myValidSystems = new HashSet<>(); + private CachingValidationSupport myValidationSupport; private void addValidConcept(String theSystem, String theCode) { myValidSystems.add(theSystem); @@ -106,8 +112,9 @@ public class FhirInstanceValidatorR5Test { myVal.setValidateAgainstStandardSchematron(false); myMockSupport = mock(IValidationSupport.class); - CachingValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(myMockSupport, myDefaultValidationSupport)); - myInstanceVal = new FhirInstanceValidator(validationSupport); + when(myMockSupport.getFhirContext()).thenReturn(ourCtx); + myValidationSupport = new CachingValidationSupport(new ValidationSupportChain(myMockSupport, myDefaultValidationSupport, new InMemoryTerminologyServerValidationSupport(ourCtx), new CommonCodeSystemsTerminologyService(ourCtx))); + myInstanceVal = new FhirInstanceValidator(myValidationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -115,11 +122,11 @@ public class FhirInstanceValidatorR5Test { myValidConcepts = new ArrayList<>(); - when(myMockSupport.expandValueSet(nullable(FhirContext.class), nullable(ConceptSetComponent.class))).thenAnswer(theInvocation -> { - ConceptSetComponent arg = (ConceptSetComponent) theInvocation.getArguments()[1]; - ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getSystem()); + when(myMockSupport.expandValueSet(any(), nullable(ValueSetExpansionOptions.class), nullable(IBaseResource.class))).thenAnswer(theInvocation -> { + ValueSet arg = (ValueSet) theInvocation.getArgument(2, IBaseResource.class); + ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getCompose().getIncludeFirstRep().getSystem()); if (retVal == null) { - ValueSetExpander.ValueSetExpansionOutcome outcome = myDefaultValidationSupport.expandValueSet(ourCtx, arg); + IValidationSupport.ValueSetExpansionOutcome outcome = myDefaultValidationSupport.expandValueSet(myDefaultValidationSupport, null, arg); return outcome; } ourLog.debug("expandValueSet({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal}); @@ -128,7 +135,7 @@ public class FhirInstanceValidatorR5Test { valueset.setExpansion(retVal); return new ValueSetExpander.ValueSetExpansionOutcome(valueset); }); - when(myMockSupport.isCodeSystemSupported(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(myMockSupport.isCodeSystemSupported(any(), nullable(String.class))).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock theInvocation) throws Throwable { boolean retVal = myValidSystems.contains(theInvocation.getArguments()[1]); @@ -136,58 +143,62 @@ public class FhirInstanceValidatorR5Test { return retVal; } }); - when(myMockSupport.fetchResource(nullable(FhirContext.class), nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { + when(myMockSupport.fetchResource(nullable(Class.class), nullable(String.class))).thenAnswer(new Answer() { @Override public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable { IBaseResource retVal; - String id = (String) theInvocation.getArguments()[2]; + String id = (String) theInvocation.getArguments()[1]; + Class clazz = (Class) theInvocation.getArguments()[0]; if ("Questionnaire/q_jon".equals(id)) { retVal = ourCtx.newJsonParser().parseResource(IOUtils.toString(FhirInstanceValidatorR5Test.class.getResourceAsStream("/q_jon.json"))); } else { - retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class) theInvocation.getArguments()[1], id); + retVal = myDefaultValidationSupport.fetchResource(clazz, id); } - ourLog.debug("fetchResource({}, {}) : {}", theInvocation.getArguments()[1], id, retVal); + ourLog.debug("fetchResource({}, {}) : {}", clazz, id, retVal); return retVal; } }); - when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { + when(myMockSupport.validateCode(any(), any(), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer() { @Override - public IContextValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { - FhirContext ctx = theInvocation.getArgument(0, FhirContext.class); - String system = theInvocation.getArgument(1, String.class); - String code = theInvocation.getArgument(2, String.class); - String display = theInvocation.getArgument(3, String.class); - String valueSetUrl = theInvocation.getArgument(4, String.class); - IContextValidationSupport.CodeValidationResult retVal; + public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) { + ConceptValidationOptions options = theInvocation.getArgument(1, ConceptValidationOptions.class); + String system = theInvocation.getArgument(2, String.class); + String code = theInvocation.getArgument(3, String.class); + String display = theInvocation.getArgument(4, String.class); + String valueSetUrl = theInvocation.getArgument(5, String.class); + IValidationSupport.CodeValidationResult retVal; if (myValidConcepts.contains(system + "___" + code)) { - retVal = new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent((code))); + retVal = new IValidationSupport.CodeValidationResult().setCode(code); + } else if (myValidSystems.contains(system)) { + return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.WARNING).setMessage("Unknown code: " + system + " / " + code); } else { - retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl); + retVal = myDefaultValidationSupport.validateCode(myDefaultValidationSupport, options, system, code, display, valueSetUrl); } ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal); return retVal; } }); - when(myMockSupport.fetchCodeSystem(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(myMockSupport.fetchCodeSystem(nullable(String.class))).thenAnswer(new Answer() { @Override public CodeSystem answer(InvocationOnMock theInvocation) { - CodeSystem retVal = myDefaultValidationSupport.fetchCodeSystem((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]); - ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal}); + String codeSystem = (String) theInvocation.getArguments()[0]; + CodeSystem retVal = (CodeSystem) myDefaultValidationSupport.fetchCodeSystem(codeSystem); + ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{codeSystem, retVal}); return retVal; } }); - when(myMockSupport.fetchStructureDefinition(nullable(FhirContext.class), nullable(String.class))).thenAnswer(new Answer() { + when(myMockSupport.fetchStructureDefinition(nullable(String.class))).thenAnswer(new Answer() { @Override public StructureDefinition answer(InvocationOnMock theInvocation) { - StructureDefinition retVal = myDefaultValidationSupport.fetchStructureDefinition((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]); - ourLog.debug("fetchStructureDefinition({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal}); + StructureDefinition retVal = (StructureDefinition) myDefaultValidationSupport.fetchStructureDefinition((String) theInvocation.getArguments()[1]); + ourLog.debug("fetchStructureDefinition({}) : {}", new Object[]{theInvocation.getArguments()[0], retVal}); return retVal; } }); - when(myMockSupport.fetchAllStructureDefinitions(nullable(FhirContext.class))).thenAnswer(new Answer>() { + when(myMockSupport.fetchAllStructureDefinitions()).thenAnswer(new Answer>() { @Override public List answer(InvocationOnMock theInvocation) { - List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions((FhirContext) theInvocation.getArguments()[0]); + List retVal = myDefaultValidationSupport.fetchAllStructureDefinitions(); ourLog.debug("fetchAllStructureDefinitions()", new Object[]{}); return retVal; } @@ -223,12 +234,12 @@ public class FhirInstanceValidatorR5Test { .setCode("AA "); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(p); List all = logResultsAndReturnErrorOnes(result); assertFalse(result.isSuccessful()); - assertEquals("The code 'AA ' is not valid (whitespace rules)", all.get(0).getMessage()); + assertEquals("The code \"AA \" is not valid (whitespace rules)", all.get(0).getMessage()); } @@ -246,12 +257,12 @@ public class FhirInstanceValidatorR5Test { ""; FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(input); List all = logResultsAndReturnAll(result); assertFalse(result.isSuccessful()); - assertEquals("Primitive types must have a value that is not empty", all.get(0).getMessage()); + assertEquals("ele-1: All FHIR elements must have a @value or children [hasValue() or (children().count() > id.count())]", all.get(0).getMessage()); } /** @@ -271,7 +282,7 @@ public class FhirInstanceValidatorR5Test { // With BPs disabled val = ourCtx.newValidator(); - instanceModule = new FhirInstanceValidator(myDefaultValidationSupport); + instanceModule = new FhirInstanceValidator(myValidationSupport); val.registerValidatorModule(instanceModule); result = val.validateWithResult(input); all = logResultsAndReturnAll(result); @@ -280,7 +291,7 @@ public class FhirInstanceValidatorR5Test { // With BPs enabled val = ourCtx.newValidator(); - instanceModule = new FhirInstanceValidator(myDefaultValidationSupport); + instanceModule = new FhirInstanceValidator(myValidationSupport); IResourceValidator.BestPracticeWarningLevel level = IResourceValidator.BestPracticeWarningLevel.Error; instanceModule.setBestPracticeWarningLevel(level); val.registerValidatorModule(instanceModule); @@ -292,7 +303,7 @@ public class FhirInstanceValidatorR5Test { private List logResultsAndReturnNonInformationalOnes(ValidationResult theOutput) { - List retVal = new ArrayList(); + List retVal = new ArrayList<>(); int index = 0; for (SingleValidationMessage next : theOutput.getMessages()) { @@ -308,7 +319,7 @@ public class FhirInstanceValidatorR5Test { } private List logResultsAndReturnErrorOnes(ValidationResult theOutput) { - List retVal = new ArrayList(); + List retVal = new ArrayList<>(); int index = 0; for (SingleValidationMessage next : theOutput.getMessages()) { @@ -378,7 +389,7 @@ public class FhirInstanceValidatorR5Test { procedure.setOccurrence(period); FhirValidator val = ourCtx.newValidator(); - val.registerValidatorModule(new FhirInstanceValidator(myDefaultValidationSupport)); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); ValidationResult result = val.validateWithResult(procedure); @@ -610,8 +621,8 @@ public class FhirInstanceValidatorR5Test { List messages = logResultsAndReturnNonInformationalOnes(output); assertEquals(output.toString(), 3, messages.size()); assertThat(messages.get(0).getMessage(), containsString("Element must have some content")); - assertThat(messages.get(1).getMessage(), containsString("Primitive types must have a value or must have child extensions")); - assertThat(messages.get(2).getMessage(), containsString("All FHIR elements must have a @value or children [hasValue() or (children().count() > id.count())]")); + assertThat(messages.get(1).getMessage(), containsString("ele-1: All FHIR elements must have a @value or children [hasValue() or (children().count() > id.count())]")); + assertThat(messages.get(2).getMessage(), containsString("Primitive types must have a value or must have child extensions")); } @Test @@ -651,7 +662,6 @@ public class FhirInstanceValidatorR5Test { @Test public void testValidateRawXmlWithMissingRootNamespace() { - //@formatter:off String input = "" + "" + " " @@ -666,11 +676,10 @@ public class FhirInstanceValidatorR5Test { + " " + " " + ""; - //@formatter:on ValidationResult output = myVal.validateWithResult(input); assertEquals(output.toString(), 1, output.getMessages().size()); - assertEquals("This 'Patient' cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); + assertEquals("This \"Patient\" cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); ourLog.info(output.getMessages().get(0).getLocationString()); } @@ -747,7 +756,7 @@ public class FhirInstanceValidatorR5Test { input.setStatus(Enumerations.ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnAll(output); @@ -768,7 +777,7 @@ public class FhirInstanceValidatorR5Test { input.setStatus(Enumerations.ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); @@ -789,10 +798,10 @@ public class FhirInstanceValidatorR5Test { input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); input.setStatus(Enumerations.ObservationStatus.FINAL); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); ValidationResult output = myVal.validateWithResult(input); List errors = logResultsAndReturnNonInformationalOnes(output); - assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); + assertThat(errors.toString(), containsString("Profile reference \"http://foo/structuredefinition/myprofile\" could not be resolved, so has not been checked")); } @Test @@ -840,7 +849,7 @@ public class FhirInstanceValidatorR5Test { ValidationResult output = myVal.validateWithResult(input); logResultsAndReturnAll(output); assertEquals( - "The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.2.0 (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code[notvalidcode] in system[(none)])", + "The value provided (\"notvalidcode\") is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.2.0 (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code 'notvalidcode')", output.getMessages().get(0).getMessage()); } @@ -849,7 +858,7 @@ public class FhirInstanceValidatorR5Test { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); input.setStatus(Enumerations.ObservationStatus.FINAL); input.getCode().addCoding().setSystem("http://loinc.org").setCode("12345"); @@ -865,7 +874,7 @@ public class FhirInstanceValidatorR5Test { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(Enumerations.ObservationStatus.FINAL); @@ -883,7 +892,7 @@ public class FhirInstanceValidatorR5Test { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(Enumerations.ObservationStatus.FINAL); @@ -903,7 +912,7 @@ public class FhirInstanceValidatorR5Test { expansionComponent.addContains().setSystem("http://loinc.org").setCode("12345").setDisplay("Some display code"); mySupportedCodeSystemsForExpansion.put("http://loinc.org", expansionComponent); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://loinc.org", "12345"); input.setStatus(Enumerations.ObservationStatus.FINAL); @@ -920,7 +929,7 @@ public class FhirInstanceValidatorR5Test { Observation input = new Observation(); input.getText().setDiv(new XhtmlNode().setValue("
        AA
        ")).setStatus(Narrative.NarrativeStatus.GENERATED); - myInstanceVal.setValidationSupport(myMockSupport); + myInstanceVal.setValidationSupport(myValidationSupport); addValidConcept("http://acme.org", "12345"); input.setStatus(Enumerations.ObservationStatus.FINAL); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/QuestionnaireResponseValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/QuestionnaireResponseValidatorR5Test.java index 6e8911c1e41..d4090b0cc9f 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/QuestionnaireResponseValidatorR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/QuestionnaireResponseValidatorR5Test.java @@ -1,26 +1,24 @@ package org.hl7.fhir.r5.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.ValidationResult; -import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType; import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseStatus; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.AfterClass; import org.junit.Before; @@ -49,8 +47,8 @@ public class QuestionnaireResponseValidatorR5Test { private static final String CODE_ICC_SCHOOLTYPE_PT = "PT"; private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype"; private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype"; - private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(); private static FhirContext ourCtx = FhirContext.forR5(); + private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); private FhirInstanceValidator myInstanceVal; private FhirValidator myVal; private IValidationSupport myValSupport; @@ -59,14 +57,15 @@ public class QuestionnaireResponseValidatorR5Test { @Before public void before() { myValSupport = mock(IValidationSupport.class); - // new DefaultProfileValidationSupport(); + when(myValSupport.getFhirContext()).thenReturn(ourCtx); + myWorkerCtx = new HapiWorkerContext(ourCtx, myValSupport); myVal = ourCtx.newValidator(); myVal.setValidateAgainstStandardSchema(false); myVal.setValidateAgainstStandardSchematron(false); - ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport); + ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport, new InMemoryTerminologyServerValidationSupport(ourCtx)); myInstanceVal = new FhirInstanceValidator(validationSupport); myVal.registerValidatorModule(myInstanceVal); @@ -91,16 +90,16 @@ public class QuestionnaireResponseValidatorR5Test { codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCode(any(), any(), any(), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); - when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); + when(myValSupport.validateCode(any(), any(), any(), any(), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); + when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); int itemCnt = 16; QuestionnaireItemType[] questionnaireItemTypes = new QuestionnaireItemType[itemCnt]; @@ -161,7 +160,7 @@ public class QuestionnaireResponseValidatorR5Test { qa.setQuestionnaire("http://example.com/Questionnaire/q" + i); qa.addItem().setLinkId(linkId).addAnswer().setValue(answerValues[i]); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); @@ -182,7 +181,7 @@ public class QuestionnaireResponseValidatorR5Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaireElement().getValue()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaireElement().getValue()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); @@ -196,35 +195,35 @@ public class QuestionnaireResponseValidatorR5Test { Questionnaire q = new Questionnaire(); q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setAnswerValueSet("http://somevalueset"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true); when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCodeInValueSet(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); QuestionnaireResponse qa; ValidationResult errors; @@ -250,7 +249,7 @@ public class QuestionnaireResponseValidatorR5Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)")); + assertThat(errors.toString(), containsString("Unknown code for \"http://codesystems.com/system#code1\"")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); qa = new QuestionnaireResponse(); @@ -261,7 +260,7 @@ public class QuestionnaireResponseValidatorR5Test { errors = myVal.validateWithResult(qa); errors = stripBindingHasNoSourceMessage(errors); ourLog.info(errors.toString()); - assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3")); + assertThat(errors.toString(), containsString("Unknown code 'http://codesystems.com/system2#code3' for \"http://codesystems.com/system2#code3\"")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); } @@ -278,7 +277,7 @@ public class QuestionnaireResponseValidatorR5Test { QuestionnaireResponseItemComponent qaGroup = qa.addItem(); qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -298,7 +297,7 @@ public class QuestionnaireResponseValidatorR5Test { QuestionnaireResponseItemComponent qaItem = qa.addItem().setLinkId("link0"); qaItem.addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -319,7 +318,7 @@ public class QuestionnaireResponseValidatorR5Test { qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); String reference = qa.getQuestionnaire(); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -344,22 +343,21 @@ public class QuestionnaireResponseValidatorR5Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) - .thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))).thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(codeValue))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -401,22 +399,21 @@ public class QuestionnaireResponseValidatorR5Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl(codeSystemUrl); codeSystem.addConcept().setCode(codeValue); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl))) - .thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq(codeSystemUrl))).thenReturn(codeSystem); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef))) + when(myValSupport.fetchResource(eq(ValueSet.class), eq(valueSetRef))) .thenReturn(options); - when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent((codeValue)))); + when(myValSupport.validateCode(any(), any(), eq(codeSystemUrl), eq(codeValue), any(String.class), anyString())) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode(codeValue)); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); String qXml = xmlParser.encodeResourceToString(q); @@ -454,7 +451,7 @@ public class QuestionnaireResponseValidatorR5Test { Questionnaire q = new Questionnaire(); q.addItem(item1); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))) .thenReturn(q); IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true); @@ -487,7 +484,7 @@ public class QuestionnaireResponseValidatorR5Test { .setRequired(true); String reference = "http://example.com/Questionnaire/q1"; - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))) + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(reference))) .thenReturn(q); QuestionnaireResponse qa = new QuestionnaireResponse(); @@ -516,29 +513,29 @@ public class QuestionnaireResponseValidatorR5Test { Questionnaire q = new Questionnaire(); QuestionnaireItemComponent item = q.addItem(); item.setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.OPENCHOICE).setAnswerValueSet("http://somevalueset"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setUrl("http://codesystems.com/system"); codeSystem.addConcept().setCode("code0"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system"))).thenReturn(codeSystem); CodeSystem codeSystem2 = new CodeSystem(); codeSystem2.setContent(CodeSystemContentMode.COMPLETE); codeSystem2.setUrl("http://codesystems.com/system2"); codeSystem2.addConcept().setCode("code2"); - when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); + when(myValSupport.fetchCodeSystem(eq("http://codesystems.com/system2"))).thenReturn(codeSystem2); ValueSet options = new ValueSet(); options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); + when(myValSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent().setCode("code0"))); - when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IContextValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); + when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code")); QuestionnaireResponse qa; ValidationResult errors; @@ -600,7 +597,7 @@ public class QuestionnaireResponseValidatorR5Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -618,7 +615,7 @@ public class QuestionnaireResponseValidatorR5Test { qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1"); qa.addItem().setLinkId("link1").addItem().setLinkId("link2"); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); @@ -677,9 +674,9 @@ public class QuestionnaireResponseValidatorR5Test { .addAnswer() .setValue(new Coding(SYSTEMURI_ICC_SCHOOLTYPE, CODE_ICC_SCHOOLTYPE_PT, "")); - when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(questionnaire); - when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE))).thenReturn(iccSchoolTypeVs); - when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(ValueSet.class) )).thenReturn(new IContextValidationSupport.CodeValidationResult(new ConceptDefinitionComponent(CODE_ICC_SCHOOLTYPE_PT))); + when(myValSupport.fetchResource(eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(questionnaire); + when(myValSupport.fetchResource(eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE))).thenReturn(iccSchoolTypeVs); + when(myValSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any(ValueSet.class))).thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); ValidationResult errors = myVal.validateWithResult(qa); ourLog.info(errors.toString()); diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 4caaad9f81f..fc2f9f2cdc9 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml @@ -58,37 +58,37 @@ ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-hl7org-dstu2 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-r4 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-r5 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu3 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-r4 - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT org.apache.velocity diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java index 5863caf9dee..b0ace6cc11f 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java @@ -23,19 +23,17 @@ package ca.uhn.fhir.model.dstu2; import java.io.InputStream; import java.util.Date; +import ca.uhn.fhir.fhirpath.IFhirPath; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.*; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.fluentpath.IFluentPath; import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.base.composite.*; import ca.uhn.fhir.model.dstu2.composite.*; import ca.uhn.fhir.model.dstu2.resource.StructureDefinition; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.provider.dstu2.Dstu2BundleFactory; import ca.uhn.fhir.util.ReflectionUtil; public class FhirDstu2 implements IFhirVersion { @@ -43,16 +41,11 @@ public class FhirDstu2 implements IFhirVersion { private String myId; @Override - public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) { + public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) { throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts"); } - @Override - public IContextValidationSupport createValidationSupport() { - throw new UnsupportedOperationException("Validation support is not supported in DSTU2 contexts"); - } - @Override public IResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase) { StructureDefinition retVal = new StructureDefinition(); diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm index 976ec354e00..28195270c93 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm @@ -7,7 +7,7 @@ import java.util.concurrent.Executors; import javax.persistence.EntityManager; import org.springframework.transaction.PlatformTransactionManager; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; @@ -21,8 +21,9 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.jpa.api.dao.*; import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; @Configuration public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jpa.config${package_suffix}.Base${versionCapitalized}Config { diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 55d2ca62a4f..61fb0758709 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu21/XmlParserDstu2_1Test.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu21/XmlParserDstu2_1Test.java index bb6f110c528..be69190807f 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu21/XmlParserDstu2_1Test.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu21/XmlParserDstu2_1Test.java @@ -644,7 +644,6 @@ public class XmlParserDstu2_1Test { //@formatter:off assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -659,7 +658,6 @@ public class XmlParserDstu2_1Test { "", "", "", - "", "", "", "", @@ -706,7 +704,6 @@ public class XmlParserDstu2_1Test { //@formatter:off assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -719,7 +716,6 @@ public class XmlParserDstu2_1Test { "", "", "", - "", "", "", "", diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/Dstu3XmlParserTest.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/Dstu3XmlParserTest.java index 8b27b02d6cd..ce67997536f 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/Dstu3XmlParserTest.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/Dstu3XmlParserTest.java @@ -755,7 +755,6 @@ public class Dstu3XmlParserTest { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -770,7 +769,6 @@ public class Dstu3XmlParserTest { "", "", "", - "", "", "", "", @@ -815,7 +813,6 @@ public class Dstu3XmlParserTest { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", "", "", "", @@ -828,7 +825,6 @@ public class Dstu3XmlParserTest { "", "", "", - "", "", "", "", diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java index 356ef1b1d3a..c2833b3395c 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java @@ -10,7 +10,7 @@ import ca.uhn.fhir.tests.integration.karaf.ValidationConstants; import ca.uhn.fhir.validation.*; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; import org.hamcrest.core.StringContains; -import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.*; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/pom.xml b/pom.xml index ef8d1092440..5bd3e7996b9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. https://hapifhir.io @@ -76,14 +76,14 @@ test - hamcrest-core org.hamcrest + hamcrest-core org.hamcrest - java-hamcrest + hamcrest test @@ -595,6 +595,27 @@ Trifork Tue Toft Nørgård + + augla + August Langhout + + + dgileadi + David Gileadi + + + ibrohimislam + Ibrohim Kholilul Islam + + + mkucharek + Maciej Kucharek + + + Thopap + Thomas Papke + InterComponentWare AG + @@ -606,7 +627,7 @@ - 4.2.1-SNAPSHOT + 4.2.10-SNAPSHOT 1.0.2 -Dfile.encoding=UTF-8 -Xmx2048m @@ -633,7 +654,7 @@ 2.3.4 2.3.3 - 28.0-jre + 28.2-jre 2.8.5 2.2.11_1 2.3.1 @@ -645,11 +666,11 @@ 3.0.2 6.1.0 - 5.4.6.Final + 5.4.14.Final - 5.11.3.Final + 5.11.5.Final 5.5.5 - 5.4.2.Final + 6.1.3.Final 4.4.11 4.5.9 2.10.0 @@ -666,7 +687,7 @@ 5.2.3.RELEASE 2.2.0.RELEASE - 2.2.0.RELEASE + 2.2.6.RELEASE 1.2.2.RELEASE 3.1.4 @@ -679,6 +700,7 @@ UTF-8 1.0.1 + 1.13.0 5.4.1 @@ -1036,6 +1058,11 @@ httpcore ${httpcore_version} + + co.elastic.apm + apm-agent-api + ${elastic_apm_version} + org.apache.jena apache-jena-libs @@ -1268,16 +1295,27 @@ jscience 4.3.1 + org.hamcrest java-hamcrest 2.0.0.0 + + org.hamcrest + hamcrest + 2.2 + org.hibernate hibernate-core ${hibernate_version} + + org.hibernate + hibernate-java8 + ${hibernate_version} + org.hibernate hibernate-ehcache @@ -1289,7 +1327,7 @@ ${hibernate_version} - org.hibernate + org.hibernate.validator hibernate-validator ${hibernate_validator_version} @@ -1303,6 +1341,11 @@ hibernate-search-elasticsearch ${hibernate_search_version} + + org.hibernate + hibernate-search-engine + ${hibernate_search_version} + org.javassist javassist @@ -1321,12 +1364,12 @@ org.postgresql postgresql - 42.2.9 + 42.2.10 org.quartz-scheduler quartz - 2.3.1 + 2.3.2 org.slf4j @@ -1506,6 +1549,7 @@ + @@ -1943,9 +1988,10 @@ 3.3.9 - 1.8 + 11 - The hapi-fhir Maven build requires JDK version 1.8.x. + HAPI FHIR is still targeting JDK 8 for published binaries, but we require JDK 11 + to build and test this library. @@ -1953,6 +1999,26 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-property + + enforce + + + + + 11 + + + true + + + + maven-antrun-plugin false @@ -2430,51 +2496,13 @@ - - SITE - - hapi-fhir-base - hapi-fhir-structures-dstu2 - hapi-fhir-structures-dstu3 - hapi-fhir-structures-r4 - hapi-fhir-structures-r5 - hapi-fhir-client - hapi-fhir-server - hapi-fhir-jpaserver-model - hapi-fhir-jpaserver-searchparam - hapi-fhir-jpaserver-subscription - hapi-fhir-jpaserver-base - hapi-fhir-jaxrsserver-base - - examples - - - - - org.codehaus.mojo - findbugs-maven-plugin - 3.0.5 - - ./hapi-fhir-base/target/classes - - - - - findbugs - - - - - - - ALLMODULES true - hapi-fhir-bom + hapi-fhir-bom hapi-deployable-pom hapi-fhir-base hapi-fhir-docs @@ -2498,6 +2526,7 @@ hapi-fhir-structures-r5 hapi-fhir-validation-resources-r5 hapi-fhir-igpacks + hapi-fhir-jpaserver-api hapi-fhir-jpaserver-model hapi-fhir-jpaserver-searchparam hapi-fhir-jpaserver-subscription @@ -2512,7 +2541,6 @@ hapi-fhir-android hapi-fhir-cli hapi-fhir-dist - examples diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml index 4813953ee4a..7e6fd15196a 100644 --- a/restful-server-example/pom.xml +++ b/restful-server-example/pom.xml @@ -8,7 +8,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../pom.xml diff --git a/src/changes/changes.xml b/src/changes/changes.xml deleted file mode 100644 index 498233d327a..00000000000 --- a/src/changes/changes.xml +++ /dev/null @@ -1,7460 +0,0 @@ - - - - James Agnew - HAPI FHIR Changelog - - - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Jetty (CLI): 9.4.14.v20181114 -> 9.4.23.v20191118
      • - - ]]> -
        - - As of FHIR R4, some fields that were previously of type reference are now of type canonical. - One example is QuestionnaireResponse.questionnaire. Technically this means that this field - should no longer contain a relative reference, but as they are sometimes used that way, HAPI - FHIR will now try to be permissive and will index relative link canonical fields - such as (Questionnaire/123) as though it actually was a local relative link. Thanks to - Dean Atchley for reporting and providing a test case! - - - ValueSet PreCalculation did not successfully expand valuesets when Lucene was not enabled - in the JPA server. This has been corrected. - - - When parsing Bundle resources containing other resources, XML/JSON parsers have an option called - "override resource ID with bundle entry fullUrl". This option previously caused any value - found in Bundle.entry.fullUrl to override any value found in - Bundle.entry.resource.id (meaning that the parsed resource would take its ID from - the fullUrl even if that ID disagreed with the ID found in the resource itself. As of - HAPI FHIR 4.1.0 the value in Bundle.entry.fullUrl will only be used to set the parsed resource - ID if the resource has no ID present. - - - Chained searches using the _has search parameter as the chain value are now supported by - the JPA server. - - - Changed database migration to use flyway. This adds a new table called FLY_HFJ_MIGRATION that records all - database migration tasks that have already been applied. The hapi-fhir-cli migrate tool has been changed - to use flyway. Learn more about flyway here: https://flywaydb.org/. - - - ValueSet Precalculation sometimes failed on Oracle DBs due to an invalid SQL function. This - has been corrected. - - - A ConcurrentModificationException was sometimes thrown when performing a cascading delete. - This has been corrected. - - - The constructor for Verdict.java was inadvertantly made private, preventing custom - rules from being written. Thanks to Jafer Khan for the pull request! - - - The - IRestfulClient#registerInterceptor - and - IRestfulClient#unregisterInterceptor - ]]> - methods now take Object as an argument instead of IClientInterceptor, allowing - client interceptors to now be migrated to the new interceptor - framework. - - - A missing mandatory was added to the SNOMED CT CodeSystem that is uploaded when SCT is - uploaded to the JPA server. Thanks to Anders Havn for the pull request! - - - An issue with the HAPI FHIR CLI was corrected that prevented the upload of LOINC due to an error - regarding the properties file. - -
        - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • SLF4j (All): 1.7.25 -> 1.7.28
      • -
      • Spring (JPA): 5.1.8.Final -> 5.2.0.Final
      • -
      • Hibernate Core (JPA): 5.4.2.Final -> 5.4.6.Final
      • -
      • Hibernate Search (JPA): 5.11.1.Final -> 5.11.3.Final
      • -
      • Jackson Databind (JPA): 2.9.9 -> 2.9.10 (CVE-2019-16335, CVE-2019-14540)
      • -
      • Commons-DBCP2 (JPA): 2.6.0 -> 2.7.0
      • -
      • Postgresql JDBC Driver (JPA): 42.2.6.jre7 -> 42.2.8
      • -
      • MSSQL JDBC Driver (JPA): 7.0.0.jre8 -> 7.4.1.jre8
      • -
      • Spring Boot (Boot): 2.1.1 -> 2.2.0
      • -
      • Phloc Schematron (Validator): 5.0.4 -> 5.2.0
      • -
      • Phloc Commons (Validator): 9.1.1 -> 9.3.8
      • - - ]]> -
        - - - New Feature: - The JPA server now saves and supports searching on Resource.meta.source via the - _source search parameter. The server automatically - appends the Request ID as a hash value on the URI as well in order to provide request level tracking. Searches - can use either the source URI, the request ID, or both. - ]]> - - - New Feature: - Support for the FHIR Bulk Data Export specification has been added to the JPA server. See the - specification for information on how this works. Note that - only system level export is currently supported but others will follow. - ]]> - - - New Feature: - Support for ElasticSearch has been added to the JPA server directly (i.e. without needing a separate - module) and a new class called "ElasticsearchHibernatePropertiesBuilder" has been added to facilitate - the creation of relevant properties. Instructions have been added to the hapi-fhir-jpaserver-starter - project to get started with Elasticsearch. It is likely we will switch our default recommendation - to Elastic in the future. - ]]> - - - New Feature: - A new set of operations have been added to the JPA server that allow CodeSystem deltas to be - uploaded. A CodeSystem Delta consists of a set of codes and relationships that are added or - removed incrementally to the live CodeSystem without requiring a downtime or a complete - upload of the contents. Deltas may be specified using either a custom CSV format or a partial - CodeSystem resource. -
        - In addition, the HAPI FHIR CLI - upload-terminology command has been modified to support this new functionality. - ]]> -
        - - New Feature: - When using Externalized Binary Storage in the JPA server, the system will now automatically - externalize Binary and Attachment payloads, meaning that these will automatically not be - stored in the RDBMS. - ]]> - - - Model Update: - The DSTU3 structures have been upgraded to the new 3.0.2 (Technical Correction) release.
        - The R4 structures have been upgraded to the new 4.0.1 (Technical Correction) release.
        - The R5 structure have been upgraded to the current (October) snapshot. - ]]> -
        - - Performance Improvement: - A significant performance improvement was made to the parsers (particularly the Json Parser) - when serializing resources. This work yields improvements of 20-50% in raw encode speed when - encoding large resources. Thanks to David Maplesden for the pull request! - ]]> - - - Performance Improvement: - When running inside a JPA server, The DSTU3+ validator now performs code validations - by directly testing ValueSet membership against a pre-calculated copy of the ValueSet, - instead of first expanding the ValueSet and then examining the expanded contents. - This can yield a significant improvement in validation speed in many cases. - ]]> - - - The email Subscription deliverer now respects the payload property of the subscription - when deciding how to encode the resource being sent. Thanks to Sean McIlvenna for the - pull request! - - - When using the _filter search parameter, string comparisons using the "eq" operator - were incorrectly performing a partial match. This has been corrected. Thanks to - Marc Sandberg for pointing this out! - - - When using the AuthorizationInterceptor with a rule to allow all reads by resource type, - the server will now reject requests for other resource types earlier in the processing - cycle. Thanks to Anders Havn for the suggestion! - - - Reference search parameters did not work via the _filter parameter - - - Transaction entries with a resource URL starting with a leading - slash (e.g. - /Organization?identifier=foo]]> - instead of - Organization?identifier=foo]]> - did not work. These are now supported. - - - SubscriptionDstu2Config incorrectly pointed to a DSTU3 configuration file. This - has been corrected. - - - When using the VersionedApiConverterInterceptor, GraphQL responses failed with an HTTP - 500 error. - - - Cascading deletes now correctly handle circular references. Previously this failed with - an HTTP 500 error. - - - The informational message returned in an OperationOutcome when a delete failed due to cascades not being enabled - contained an incorrect example. This has been corrected. - - - In some cases, deleting a CodeSystem resource would fail because the underlying - codes were not correctly deleted from the terminology service tables. This is - fixed. - - - Two foreign keys have been dropped from the HFJ_SEARCH_RESULT table used by the FHIR search query cache. These - constraints did not add value and caused unneccessary contention when used under high load. - - - An inefficient regex expression in UrlUtil was replaced with a much more efficient hand-written - checker. This regex was causing a noticable performance drop when feeding large numbers of transactions - into the JPA server at the same time (i.e. when loading Synthea data). - - - The FHIRPath engine used to parse search parameters in the JPA R4/R5 server is now reused across - requests, as it is somewhat expensive to create and is thread safe. - - - It is now possible to submit a PATCH request as a part of a FHIR transaction in DSTU3 (previously this - was only supported in R4+). This is not officially part of the DSTU3 spec, but it can now be performed by - leaving the Bundle.entry.request.method blank in DSTU3 transactions and setting the request payload - as a Binary resource containing a valid patch. - - - The HAPI FHIR CLI server now uses H2 as its database platform instead of Derby. - Note that this means that data in any existing installations will need to be - re-uploaded to the new database platform. - - - LOINC concepts now include multiaxial hierarchical properties (e.g. parent]]> and - child]]>, which identify parent and child concepts. - - - When loading LOINC terminology, a new ValueSet is automatically created with a single include element that - identifies the LOINC CodeSystem in ValueSet.compose.include.system]]>. This ValueSet - includes all LOINC codes. - - - A note has been added to the downloads page explaning the removal of the hapi-fhir-utilities - module. Thanks to Andrew Fitzgerald for the PR! - - - REST servers will no longer try to guess the content type for HTTP requests where a body - is provided but no Content-Type header is included. These requests are invalid, and will now - result in an HTTP 400. This change corrects an error where some interceptors (notably - the RequestValidatingInterceptor, but not including any HAPI FHIR security interceptors) - could be bypassed if a Content Type was not included. - - - The GraphQL provider did not wrap the respone in a "data" element as described in the FHIR - specification. This has been corrected. - - - Added support for comparing resource dates to the current time via a new variable %now. E.g. - Procedure?date=gt%now would match future procedures. - - - Add support for in-memory matching on date comparisons ge,gt,eq,lt,le. - - - When using the Consent Service and denying a resource via the "Will See Resource" method, the resource ID - and version were still returned to the user. This has been corrected so that no details about - the resource are leaked. - - - Fix a failure in FhirTerser#visit when fields in model classes being visited contain custom subclasses of the - expected type. - - - Updating an existing CodeSystem resource with a content mode of COMPLETE did not cause the - terminology service to accurately reflect the new CodeSystem URL and/or concepts. This is now - corrected. - - - The JPA server now uses the Quartz scheduling library as a lob scheduling mechanism - - - The Testpage Overlay has been upgraded to use FontAwesome 5.x, and now supports being - deployed to a servlet path other than "/". - - - A NullPointerException when using the AuthorizationInterceptor RuleBuilder to build a conditional - rule with a custom tester has been corrected. Thanks to Tue Toft Nørgård for reporting! - - - The R4+ client and server modules did not recognize the new - _include:iterate]]> - syntax that replaces the previous - _include:recurse]]> - syntax. Both are now supported on all servers in order to avoid breaking backwards - compatibility, with the new syntax now being emitted in R4+ clients. - - - The hapi-fhir-jaxrs-server module now lists dependencies on structures JARs as optional - dependencies, in order to avoid automatically importing all versions. This means that implementers - of JAX-RS servers may now need to add an explicit dependency on one or more structures JARs to - their own project. - - - The LOINC terminology distribution includes multiple copies of the same files. Uploading LOINC terminology - resulted in some ValueSets with duplicate codes. This has been corrected by specifying a path with each - filename. - - - A corner case bug in the JPA server was solved: When performing a search that contained chained reference searches - where the value contained slashes (e.g. - Observation?derived-from:DocumentReference.contenttype=application/vnd.mfer]]>) - the server could fail to load later pages in the search results. - - - A new flag has been added to the JPA migrator tool that causes the migrator to not try to reduce the length - of existing columns in the schema. - - - Some resource IDs and URLs for LOINC ValueSets and ConceptMaps were inconsistently populated by the - terminology uploader. This has been corrected. - - - When a resource was updated with a meta.source containing a request id, the meta.source was getting appended - with the new request id, resulting in an ever growing source.meta value. E.g. after the first update, it looks - like "#9f0a901387128111#5f37835ee38a89e2" when it should only be "#5f37835ee38a89e2". This has been corrected. - - - The Plain Server method selector was incorrectly allowing client requests with _include statements to be - handled by method implementations that did not have any @IncludeParam]]> defined. This - is now corrected. Thanks to Tuomo Ala-Vannesluoma for reporting and providing a test case! - - - The ValueSet operation $expand]]> has been optimized for large ValueSets. ValueSets are - now persistence-backed by the terminology tables, which are populated by a scheduled pre-expansion process. - A ValueSet previously stored in an existing FHIR repository will need to be re-created or updated to make - it a candidate for pre-expansion. ValueSets that have yet to be pre-expanded will continue to be expanded - in-memory. - - - The ValueSet operation $validate-code]]> has been optimized for large ValueSets. - Codes in ValueSets that have yet to be pre-expanded will continue to be validated in-memory. - - - LOINC filenames for terminology upload are now configurable using the - loincupload.properties]]> file. - - - Support for the LOINC EXTERNAL_COPYRIGHT_NOTICE]]> property and - copyright]]> filter has been added. - - - Support for the LOINC parent]]> and child]]> filters has been - added. Both filters can be used with either of the =]]> or - in]]> operators. - - - Support for the LOINC ancestor]]> and descendant]]> filters has - been added. The descendant]]> filter can be used with either of the - =]]> or in]]> operators. At present, the - ancestor]]> filter can only be used with the =]]> operator. - - - Support for the LOINC ancestor]]> filter with the in]]> - operator has been added. - - - The JPA server failed to find codes defined in not-present codesystems in some cases, and reported - that the CodeSystem did not exist. This has been corrected. - - - The method - IVersionSpecificBundleFactory#initializeBundleFromResourceList - ]]> - has been deprecated, as it provided duplicate functionality to other methods and had an - outdated argument list based on the Bundle needs in DSTU1. We are not aware of any - public use of this API, please let us know if this deprecation causes any issues. - - - Support for concept property values with a length exceeding 500 characters has been added in the terminology - tables. In particular, this was added to facilitate the LOINC EXTERNAL_COPYRIGHT_NOTICE property, for which - values can be quite long. - - - The AuthorizationInterceptor has been enhanced so that a user can be authorized to - perform create operations specifically, without authorizing all write operations. Also, - conditional creates can now be authorized even if they are happening inside a FHIR - transaction. - - - When encoding a Composition resource in XML, the section narrative blocks were incorrectly - replaced by the main resource narrative. Thanks to Mirjam Baltus for reporting! - - - AN issue with date pickers not working in the hapi-fhir-testpage-overlay - project has been fixed. Thanks to GitHub user @jaferkhan for the pull - request! - - - A docker compose script for the hapi-fhir-jpaserver-starter project was added. Thanks to - Long Nguyen for the pull request! - - - A number of overridden methods in the HAPI FHIR codebase did not have the - @Override annotation. Thanks to Clayton Bodendein for cleaning this up! - - - Plain server resource providers were not correctly matching methods that - had the _id search parameter if a client performed a request using a modifier - such as :not or :exact. Thanks to Petro Mykhailyshyn - for the pull request! - - - The JPA server contained a restriction on the columns used to hold a resource's type name - that was too short to hold the longest name from the final R4 definitions. This has been - corrected to account for names up to 40 characters long. - - - The subscription triggering operation was not able to handle commas within search URLs being - used to trigger resources for subscription checking. This has been corrected. - - - In some cases where resources were recently expunged, null entries could be passed to JPA interceptors registered - against the STORAGE_PRESHOW_RESOURCES hook. - - - In issue was fixed in the JPA server where a previously failed search would be reused, - immediately returning an error rather than retrying the search. - - - The JPA server did not correctly process _has queries where the linked search parameter was - the _id parameter. - - - HTTP PUT (resource update) operations will no longer allow the version to be specified in a - Content-Location header. This behaviour was allowed in DSTU1 and was never removed from HAPI even though - it hasn't been permitted in the spec for a very long time. Hopefully this change will not - impact anyone! - - - HAPI FHIR allows transactions in DSTU3 to contain a JSON/XML Patch in a Binary resource without - specifying a verb in Bundle.entry.request.method, since the valueset defined in DSTU3 for that - field does not include the PATCH verb. The AuthorizationInterceptor however did not understand - this and would reject these requests. This is now corrected. - - - A potential XXE vulnerability in the validator was corrected. The XML parser used for validating - XML payloads (i.e. FHIR resources) will no longer read from DTD declarations. - - - Auto generated transaction IDs will now use both upper- and lowercase letters for more uniqueness in - the same amount of space. - - - Paging requests that are incorrectly executed at the type level were interpreted by the plain server - as search requests with no search parameters, leading to confusing search results. These will now - result in an HTTP 400 error with a meaningful error message. - - - The - IValidationSupport#validateCode(...)]]> - method has been modified to add an additional parameter (String theValueSetUrl). - Most users will be unaffected by this change as HAPI FHIR provides a number of - built-in implementations of this interface, but any direct user implementations - of this interface will need to add the new parameter. - - - The server CapabilityStatement (/metadata) endpoint now respects the Cache-Control header. Thanks - to Jens Villadsen for the pull request! - - - The @Metadata annotation now has an attribute that can be used to control - the cache timeout - - - QuestionnaireResponse validation in the JPA server was not able to load Questionnaire resources that - were referenced using a canonical URI instead of a local reference. Thanks to Vu Vuong for reporting! - - - When validating JSON payloads, the JSON structure was parsed by Gson instead of passing the - raw JSON to the validator. This meant that the validator was unable to catch certain structural - errors that are ignored by Gson. Thanks to Michael Lawley for reporting! - - - The JPA server exposed a number of duplicate entries in the CapabilityStatement's list of - supported _include values for a given resource. Thanks to Jens Villadsen for reporting! - - - An unintended dependency from hapi-fhir-base on Jetty was introduced in HAPI FHIR 4.0.0. This - has been removed. - - - The JPA migrator tool was not able to correctly drop tables containing foreign key references - in some cases. This has been corrected. - - - An issue was fixed where the JPA server would occasionally fail to save a resource because - it contained a string containing characters that change length when normalized. Thanks to - Tuomo Ala-Vannesluoma for the pull request! - - - Validation errors will now include details about the line number where the issue was found - - - The hapi-fhir-testpage-overlay project now uses WebJars for importing - JavaScript dependency libraries. This reduces our Git repository size and - should make it easier to stay up-to-date. - - - In the (fairly unlikely) circumstance that a JPA server was called with a parameter where the parameter name referenced - a custom search parameter with an invalid chain attached, and the value was missing entirely (e.g. - ProcedureRequest?someCustomParameter.BAD_NAME=]]>, the server would ignore this - parameter instead of incorrectly returning an error. This has been corrected. - - - Several issues with HAPI FHIR's annotation scanner that prevented use with Kotlin based - resource providers have been corrected. Thanks to Jelmer ter Wal for the pull request! - - - The JPA server in DST2 mode previously automatically validated submitted QuestionnaireResponse resource against - their corresponding Questionnaires and rejected non-conformant QuestionnaireResponse resources from being - stored. This was in contrast to every other version where the - RequestValidatingInterceptor has to be registered in order to achieve this specific behaviour. This - behaviour has been removed from the JPA server, and the same interceptor must be used for - QR validation in the DSTU2 JPA server as in all other versions of FHIR. - - - DSTU2.1 profile validation now uses the same R5 validation as all other versions of FHIR. - - - A new built-in server interceptor called - CaptureResourceSourceFromHeaderInterceptor]]> - has been added. - This interceptor can be used to capture an incoming source system URI in an HTTP Request - Header and automatically place it in - Resource.meta.source]]> - - - The @ProvidesResources annotation has been removed from HAPI FHIR, as it was not documented - and did not do anything useful. Please get in touch if this causes any issues. - - - Search parameters of type URI did not work in the hapi-fhir-testpage-overlay. This has been corrected. - - - JPA servers accidentally stripped the type attribute from the server-exported CapabilityStatement - when search parameters of type "special" were found. This has been corrected. - - - When running the JPA server without Lucene indexing enabled and performing ValueSet expansion, - the server would incorrectly ignore inclusion rules that specified a system but no codes (i.e. - include the whole system). This has been corrected. - - - The hapi-fhir-testpage-overlay has been updated to support R5 endpoints. Thanks to Dazhi Jiao - for the pull request! - - - The CapabilityStatement generator will now determine supported profiles by navigating the complete - hierarchy of supported resource types, instead of just using the root resource for each type. - Thanks to Stig Døssing for the pull request! - - - A NullPointerException in the XML Parser was fixed when serializing a resource containing an extension - on a primitive datatype that was missing a URL declaration. - - - When using the _filter search parameter in the JPA server with an untyped resource ID, the - filter could bring in search results of the wrong type. Thanks to Anthony Sute for the Pull - Request and Jens Villadsen for reporting! - - - In some cases where where a single search parameter matches the same resource many times with - different distinct values (e.g. a search by Patient:name where there are hundreds of patients having - hundreds of distinct names each) the Search Coordinator would end up in an infinite loop and never - return all of the possible results. Thanks to @imranmoezkhan for reporting, and to - Tim Shaffer for providing a reproducible test case! - -
        - - - This release contains no new or updated functionality, but addressed a dependency - version that was left incorrectly requiring a SNAPSHOT maven build of the - org.hl7.fhir.utilities module. Users who are successfully using HAPI FHIR 4.0.0 - do not need to upgrade, but any users who were blocked from upgrading due to - snapshot dependency issues are advised to upgrade immediately. - - - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Commons Codec (Core): 1.11 -> 1.12
      • -
      • Apache HTTPClient (Client): 4.5.3 -> 4.5.9
      • -
      • Apache HTTPCore (Client>: 4.4.6 -> 4.4.11
      • -
      • Spring (JPA): 5.1.6.RELEASE -> 5.1.8.RELEASE
      • -
      • Spring-Data (JPA): 2.1.6.RELEASE -> 2.1.8.RELEASE
      • -
      • JANSI (CLI): 1.17.1 -> 1.18
      • -
      • json-patch (JPA): 1.10 -> 1.15 (see changelog entry about this change)
      • -
      • Jackson-Databind (JPA): 2.9.9 -> 2.9.9.1 (due to a Jackson vulnerability CVE-2019-12384)
      • -
      • commons-collections4 (Server/JPA): 4.1 -> 4.3
      • -
      • commons-dbcp2 (JPA): 2.5.0 -> 2.6.0
      • -
      • commons-lang3 (Core): 3.8.1 -> 3.9
      • -
      • commons-text (Core): 1.6 -> 1.7
      • -
      • Guava (JPA): 27.1-jre -> 28.0-jre
      • - - ]]> -
        - - Breaking Change: - The HL7.org DSTU2 structures (and ONLY the HL7.org DSTU2 structures) have been - moved to a new package. Where they were previously found in - org.hl7.fhir.instance.model - they are now found in - org.hl7.fhir.dstu2.model. This was done in order to complete the harmonization - between the - HAPI FHIR - GitHub repository and the - org.hl7.fhir.core - GitHub repository. This is the kind of change we don't make lightly, as we do know that it - will be annoying for users of the existing library. It is a change however that will allow us - to apply validator fixes much more quickly, and will greatly reduce the amount of effort - required to keep up with R5 changes as they come out, so we're hoping it is worth it. - Note that no classes are removed, they have only been moved, so it should be fairly straightforward - to migrate existing code with an IDE. - ]]> - - - Breaking Change: - The - IPagingProvider - interface has been - modified so that the - retrieveResultList - method now takes one additional parameter of type - RequestDetails. If you have created a custom - implementation of this interface, you can add this parameter and - ignore it if needed. The use of the method has not changed, so this - should be an easy fix to existing code. - ]]> - - - Breaking Change: - The HAPI FHIR REST client and server will now default to using JSON encoding instead of XML when - the user has not explicitly configured a preference. - ]]> - - - Breaking Change: - The JPA $upload-external-code-system operation has been moved from being a - server level operation (i.e. called on the root of the server) to being - a type level operation (i.e. called on the CodeSystem type). - ]]> - - - Breaking Change: - The FhirValidator#validate(IResource) method has been removed. It was deprecated in HAPI FHIR 0.7 and replaced with - FhirValidator#validateWithResults(IBaseResource) so it is unlikely anyone is still depending on the - old method. - ]]> - - - New Feature: - Support for the new R5 draft resources has been added. This support includes the client, - server, and JPA server. Note that these definitions will change as the R5 standard is - modified until it is released, so use with caution! - ]]> - - - New Feature: - A new interceptor called - ConsentInterceptor has been added. This interceptor allows - JPA based servers to make appropriate consent decisions related to resources that - and operations that are being returned. See - Server Security - for more information. - ]]> - - - New Feature: - The JPA server now supports GraphQL for DSTU3 / R4 / R5 servers. - ]]> - - - New Feature: - The JPA server now supports the _filter search parameter when configured to - do so. The filter search parameter - is an extremely flexible and powerful feature, allowing for advanced grouping and order of - operations on searches. It can be dangerous however, as it potentially allows users to create - queries for which no database indexes exist in the default configuration so it is disabled by - default. Thanks to Anthony Sute for the pull request and all of his support in what turned - out to be a lengthy merge! - ]]> - - - New Feature: - A new interceptor called CascadingDeleteInterceptor has been added to the - JPA project. This interceptor allows deletes to cascade when a specific - URL parameter or header is added to the request. Cascading deletes - can also be controlled by a new flag in the AuthorizationIntereptor - RuleBuilder, in order to ensure that cascading deletes are only available - to users with sufficient permission. - ]]> - - - Several enhancements have been made to the AuthorizationInterceptor]]>: - -
      • The interceptor now registers against the STORAGE_PRESHOW_RESOURCES interceptor hook, - which allows it to successfully authorize JPA operations that don't actually return resource content, - such as GraphQL responses, and resources that have been filtered using the _elements - parameter.
      • -
      • -
      • The rule list is now cached on a per-request basis, which should improve performance - ]]> -
        - - The $expunge global everything operation has been refactored to do deletes - in small batches. This change will likely reduce performance, but does allow - for the operation to succeed without timing out in larger systems. - - - The JPA server did not correctly index Timing fields where the timing contained - a period but no individual events. This has been corrected. - - - The HAPI FHIR CLI import-csv-to-conceptmap command was not accounting for byte order marks in - CSV files (e.g. some Excel CSV files). This has been fixed. - - - A bug was fixed where deleting resources within a transaction did not always correctly - enforce referential integrity even if referential integrity was enabled. Thanks to - Tuomo Ala-Vannesluoma for reporting! - - - In the JPA server, the - _total=accurate]]> - was not always respected if a previous search already existed - in the query cache that matched the same search parameters. - - - Improved stability of concurrency test framework. Thanks to Stig Døssing for the pull request! - - - Moved in-memory matcher from Subscription module to SearchParam module and renamed the result type - from SubscriptionMatchResult to InMemoryMatchResult. - - - Added some experimental version-independent model classes to ca.uhn.fhir.jpa.model.any. They permit - writing code that is version independent. - - - Added new subclass of HashMapResourceProvider called SearchableHashMapResourceProvider that uses the - in-memory matcher to search the HashMap (using a full table scan). This allows rudimentary testing - without a database. - - - Added a new interceptor hook called STORAGE_PRESTORAGE_DELETE_CONFLICTS that is invoked when a - resource delete operation is about to fail due to referential integrity conflicts. - Hooks have access to the list of resources that have references to the resource being deleted and - can delete them. The boolean return value of the hook indicates whether the server should try - checking for conflicts again (true means try again). - - - The HAPI FHIR unit test suite has been refactored to no longer rely on PortUtil to - assign a free port. This should theoretically result in fewer failed builds resulting from - port conflicts. Thanks to Stig Døssing for the pull request! - - - AuthorizationInterceptor sometimes failed with a 500 error when checking compartment - membership on a resource that has a contained subject (Patient). - - - JPA server now supports conditional PATCH operation (i.e. performing a patch - with a syntax such as - /Patient?identifier=sys|val]]>) - - - The json-patch library used in the JPA server has been changed from - java-json-tools.json-patch - ]]> - to a more active fork of the same project: - crate-metadata.json-patch. - ]]> - Thanks to Jens Villadsen for the suggestion and pull request! - - - Support has been implemented in the JPA server for the CodeSystem - $subsumes]]> - operation. - - - Uploading the LOINC/RSNA Radiology Playbook would occasionally fail when evaluating part type names - due to case sensitivity. This has been corrected. - - - A new pointcut has been added to the JPA server called - JPA_PERFTRACE_RAW_SQL]]> - that can be used to capture the raw SQL statements that are sent to the underlying database. - - - Invoking the transaction or batch operation on the JPA server would fail - with a NullPointerException if the Bundle passed in did not contain - a resource in an entry that required a resource (e.g. a POST). Thanks to - GitHub user @lytvynenko-dmitriy for reporting! - - - HAPI FHIR Server (plain, JPA, and JAX-RS) all populated Bundle.entry.result - on search result bundles, even though the FHIR specification states that this - should not be populated. This has been corrected. Thanks to GitHub user - @gitrust for reporting! - - - Creating R4 Observation resources with a value type of SampledData failed in the - JPA server because of an indexing error. Thanks to Brian Reinhold for - reporting! - - - The JPA server now rejects subscriptions being submitted with no value in - Subscription.status (this field is mandatory, but the subscription was previously ignored - if no value was provided) - - - Fix a build failure thanks to Maven pom errors. Thanks to Gary Teichrow for - the pull request! - - - The JPA server did not correctly process searches with a - _tag:not]]> - expression containing more than one comma separated value. - - - The JSON and XML parsers will now raise a warning or error with the Parser Error Handler - if an extension is being encoded that is missing a URL, or has both a value and nested - extensions on the same parent extension. - - - FHIR model classes have a method called - hasPrimitiveValue()]]> - which previously returned true if the type was a primitive datatype (e.g. StringType). - This method now only returns true if the type is a primitive datatype AND - the type actually has a value. - - - Support in the JPA Terminology Service terminology uploader has been added for - uploading the IMGT - HLA Nomenclature]]> - distribution files as a FHIR CodeSystem. Thanks to Joel Schneider for the - contribution! - - - A BOM POM has been added to the HAPI FHIR distribution, allowing users to import - the HAPI FHIR library with all of its submodules automatically sharing the same - version. Thanks to Stig Døssing for the pull request! - - - AuthorizationInterceptor will now try to block delete operations sooner - in the processing lifecycle if there is no chance they will be permitted - later (i.e. because the type is not authorized at all) - - - The HAPI FHIR server will now generate a random transaction ID to every - request and add it to the response headers. Clients may supply the transaction - header via the X-Request-ID]]> header. - - - When attempting to read a resource that is deleted, a Location header is now - returned that includes the resource ID and the version ID for the deleted - resource. - - - A number of columns in the JPA Terminology Services ConceptMap tables were not - explicitly annotated with @Column, so the DB columns that were generated had - Java ugly field names as their SQL column names. These have been renamed, and - entries in the JPA migrator tool have been added for anyone upgrading. - - - Field values with a datatype of canonical]]> were indexed as - though they were explicit resource references by the JPA server. This led to - errors about external references not being supported when uploading various - resources (e.g. Questionnaires with HL7-defined ValueSet references). This has - been corrected. Note that at this time, we do not index canonical references - at all (as we were previously doing it incorrectly). This will be improved soon. - - - IBundleProvider now has an isEmpty() method that can be used to check whether any - results exist. A default implementation has been provided, so this is not - a breaking change. - - - Server CapabilityStatement/Conformance repsonses from the /metadata endpoint will - now be cached for 60 seconds always. This was previously a configurable setting on - the ServerConformanceProvider, but it is now handled directly by the method - binding so the provider now has no responsibility for caching. - - - The OkHttp client did not correctly apply the connection timeout and - socket timeout settings to client requests. Thanks to Petro Mykhailyshyn - for the pull request! - - - A new server interceptor hook called PROCESSING_COMPLETED has been added. This - hook is called by the server at the end of processing every request (success and failure). - - - The _summary]]> element was not always respected when encoding - JSON resources. - - - The JPA server now uses the H2 database instead of the derby database to run its - unit tests. We are hoping that this cuts down on the number of false test failures - we get due to mysterious derby failures. - - - Added a new Pointcut STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING that is called at the start of - the expungeEverything operation. - - - The JPA server now has the ability to generate snapshot profiles from differential - profiles via the $snapshot operation, and will automatically generate a snapshot when - needed for validation. - - - The Base64Binary types for DSTU3+ now use a byte array internally to represent their - content, which is more efficient than storing base 64 encoded text to represent - the binary as was previously done. - - - Creating/updating CodeSystems now persist CodeSystem.concept.designation]]> to - the terminology tables. - - - Expanded ValueSets now populate ValueSet.expansion.contains.designation.language]]>. - - - @Operation methods can now declare that they will manually process the request - body and/or manually generate a response instead of letting the HAPI FHIR - framework take care of these things. This is useful for situations where - direct access to the low-level servlet streaming API is needed. - - - @Operation methods can now declare that they are global, meaning that they will - apply to all resource types (or instances of all resource types) if they - are found on a plain provider. - - - @Operation method parameters may now declare their type via a String name such as - "code" or "Coding" in an attribute in @OperationParam. This is useful if you want - to make operation methods that can operate across different versions of FHIR. - - - A new resource provider for JPA servers called - BinaryAccessProvider]]> - has been added. This provider serves two custom operations called - $binary-access-read]]> and - $binary-access-write]]> that can be used to - request binary data in Attachments as raw binary content instead of - as base 64 encoded content. - - - A few columns named 'CODE' in the JPA terminology services tables have been - renamed to 'CODEVAL' to avoid any possibility of conflicting with reserved - words in MySQL. The database migrator tool has been updated to handle this - change. - - - Support for PATCH operations performed within a transaction (using a Binary - resource as the resource type in order to hold a JSONPatch or XMLPatch body) - has been added to the JPA server. - - - Two issues in the Thymeleaf Narrative Template which caused an error when generating - a narrative on an untitled DiagnosticReport were fixed. Thanks to GitHub - user @navyflower for reporting! - - - A new attribute has been added to the @Operation annotation called - typeName]]>. This annotation can be used to specify a - type for an operation declared on a plain provider without needing to use - a specific version of the FHIR structures. - - - The $upload-external-code-system operation and the corresponding HAPI FHIR CLI command - can now be used to upload custom vocabulary that has been converted into a standard file format - defined by HAPI FHIR. This is useful for uploading large organizational code systems. - - - Two new operations, - $apply-codesystem-delta-add]]> - and - $apply-codesystem-delta-remove]]> - have been added to the terminology server. These methods allow - codes to be dynamically added and removed from external (notpresent) codesystems. - - - In the JAX-RS server, the resource type history and instance vread - operations had ambiguous paths that could lead to the wrong method - being called. Thanks to Seth Rylan Gainey for the pull request! - - - The profile validator (FhirInstanceValidator) can now be used to validate a resource - using an explicit profile declaration rather than simply relying on the declared - URL in the resource itself. - - - When using the ResponseHighlighterInterceptor, some invalid requests that would normally generate an HTTP - 400 response (e.g. an invalid _elements value) would cause an HTTP 500 crash. - - - When performing a read-if-newer operation on a plain server, the resource ID - in Resource.meta.versionId is now used if a version isn't found in the resource - ID itself. Thanks to Stig Døssing for the pull request! - - - An example datatype was corrected in the DSTU2 Identifier datatype - StructureDefinition. Thanks to Nick Robison for the pull request! - -
        - - - A potential security vulnerability in the hapi-fhir-testpage-overlay project was corrected: A URL - parameter was not being correctly escaped, leading to a potential XSS vulnerability. A big thanks to - Mudit Punia and Dushyant Garg for reporting this. - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Guava (base): 25-jre -> 27.1-jre
      • -
      • Hibernate (JPA): 5.4.1 -> 5.4.2
      • -
      • Jackson (JPA): 2.9.7 -> 2.9.8
      • -
      • Spring (JPA): 5.1.3.RELEASE -> 5.1.6.RELEASE
      • -
      • Spring-Data (JPA): 2.1.3.RELEASE -> 2.1.6.RELEASE
      • -
      • Caffeine (JPA): 2.6.2 -> 2.7.0
      • -
      • JANSI (CLI): 1.16 -> 1.17.1
      • - - - ]]> -
        - - In Servers that are configured to support extended mode - _elements]]> parameters, it is now possible to - use the :exclude modifier to exclude entire resource types. - - - RequestDetails now has methods called getAttribute and setAttribute that can - be used by interceptors to pass arbitrary data between requests. - - - The hapi-fhir-jpaserver-starter project has been updated to use a properties - file for configuration, making it much easier to get started with this - project. Thanks to Sean McIlvenna for the pull request! - - - The hapi-fhir-jpaserver-example did not have Subscription capabilities - enabled after the refactoring of how Subscriptions are enabled that - occurred in HAPI FHIR 3.7.0. Thanks to Volker Schmidt for the pull request! - - - Re-use subscription channel and handlers when a subscription is updated (unless the channel type changed). - - - When using the _elements]]> parameter on searches and reads, - requesting extensions to be included caused the extensions to be included but - not any values contained within. This has been corrected. - - - The JPA terminology service can now detect when Hibernate Search (Lucene) - is not enabled, and will perform simple ValueSet expansions without relying - on Hibenrate Search in such cases. - - - A Google Analytics script fragment was leftover in the hapi-fhir-jpaserver - example and starter projects. Thanks to Patrick Werner for removing these! - - - ParametersUtil now has a utility method that can be used to add parameter values - using the string name of the datatype (e.g. "dateTime") in order to help - building Parameters resources in a version-independent way. - - - When performing a search using the JPA server, if a search returned between 1500 - and 2000 results, a query for the final page of results would timeout due to - a page calculation error. This has been corrected. - - - In the JPA server, a much more readable error message is now returned returned when - two client threads collide while trying to simultaneously create a resource with the - same client-assigned ID. In addition, better error messages are now returned - when conflicts such as this one are hit within a FHIR transaction operation. - - - The JPA query builder has been optimized to take better advantage of SQL IN (..) expressions - when performing token searches with multiple OR values. - - - The JPA server transaction processor will now automatically detect if the request - Bundle contains multiple entries having identical conditional create operations, and - collapse these into a single operation. This is done as a convenience, since many - conversion algorithms can accidentally generate such duplicates. - - - Searching the JPA server with multiple instances of the same token search parameter - (e.g. "Patient?identifier=&identifier=b" returned no results even if resources - should have matched. Thanks to @mingdatacom for reporting! - - - A new config setting has been added to the JPA DaoConfig that disables validation - of the resource type for target resources in references. - - - HapiLocalizer can now handle message patterns with braces that aren't a part of a - message format expression. E.g. "Here is an {example}". - - - JPA searches using a Composite Unique Index will now use that index for faster - searching even if the search has _includes and/or _sorts. Previously these two - features caused the search builder to skip using the index. - - - JPA searches using a Composite Unique Index did not return the correct results if - a REFERENCE search parameter was used with arguments that consisted of - unqualified resource IDs. - - - A non-threadsafe use of DateFormat was cleaned up in the StopWatch class. - - - When performing a search in the JPA server where one of the parameters is a - reference with multiple values (e.g. Patient?organization=A,B) the generated - SQL was previously a set of OR clauses and this has been collapsed into a single - IN clause for better performance. - - - When returning the results of a history operation from a HAPI FHIR server, - any entries with a method of DELETE contained a stub resource in - Bundle.entry.resource, even though the FHIR spec states that this field - should be empty. This was corrected. - - - The hapi-fhir-testpage-overlay project no longer includes any library JARs - in the built WAR, in order to prevent duplicates and conflicts in implementing - projects. - - - Two expunge bug fixes: - The first bug is that the expunge operation wasn't bailing once it hit its limit. This resulted in a - "Page size must not be less than one!" error. - The second bug is that one case wasn't properly handled: when a resourceId with no version is provided. - This executed the case where only resource type is provided. - - - When updating a resource in the JPA server, if the contents have not actually changed - the resource version is not updated and no new version is created. In this situation, - the update time was modified however. It will no longer be updated. - - - When running the JPA server in Resource Client ID strategy mode of "ANY", using the - _id]]> search parameter could return incorrect results. This - has been corrected. - - - Performing a PUT or POST against a HAPI FHIR Server with no request body caused an - HTTP 500 to be returned instead of a more appropriate HTTP 400. This has been - corrected. - - - The fetchValueSet method on IValidationSupport implementation was not visible and could - not be overridden. Thanks to Patrick Werner for the pull reuqest! - - - The JPA server failed to index R4 reources with search parameters pointing to the Money data type. - Thanks to GitHub user @navyflower for reporting! - - - When validating DSTU3 QuestionnaireResponses that leverage the "enableWhen" functionality available - in Questionnaire resources, the validation could sometimes fail incorrectly. - - - Added new configuration parameter to DaoConfig and ModelConfig to specify the websocket context path. - (Before it was hardcoded to "/websocket"). - - - Added new IRemovableChannel interface. If a SubscriptionChannel implements this, then when a subscription - channel is destroyed (because its subscription is deleted) then the remove() method will be called on that - channel. - - - The JSON Patch provider has been switched to use the provider from the - Java JSON Tools - ]]> - project, as it is much more robust and fault tolerant. - - - Ensure that database cursors are closed immediately after performing a FHIR search. - - - When performing a JSON Patch in JPA server, the post-patched document is now validated to - ensure that the patch was valid for the candidate resource. This means that invalid patches - are caught and not just silently ignored. - - - Expunges are now done in batches in multiple threads. Both the number of expunge threads and batch size are - configurable - in DaoConfig. - - - Validation errors were fixed when using a Questionnaire with enableWhen on a question that - contains sub-items. - - - Fixed "because at least one resource has a reference to this resource" delete error message that mistakingly - reported - the target instead of the source with the reference. - - - ValidationSupportChain will now call isCodeSystemSupported() on each entry in the chain before - calling fetchCodeSystem() in order to reduce the work required by chain entries. Thanks to - Anders Havn for the suggestion! - - - In JPA server when updating a resource using a client assigned ID, if the resource was previously - deleted (meaning that the operation is actually a create), the server will now return - an HTTP 201 instead of an HTTP 200. Thanks to Mario Hyland for reporting! - - - The HAPI FHIR CLI was unable to start a server in R4 mode in HAPI FHIR 3.7.0. - This has been corrected. - - - When encoding resources, profile declarations on contained resources will now be - preserved. Thanks to Anders Havn for the pull request! - - - Two incorrect package declarations in unit tests were corrected. Thanks to github user - @zaewonyx for the PR! - - - The InstanceValidator now supports validating QuestionnairResponses with empty items - for disabled questions. Thanks to Matti Uusitalo for the pull request! - - - A new method has been added to the client that allows arbitrary headers to be easily - added to the request. Thanks to Christian Ohr for the pull request! - - - VersionConverter for R2-R3 has been modified to correectly handle the renamed basedOn - field. Thanks to Gary Graham for the pull request! - - - The JPA database migration tool has been enhanced to support migration from HAPI FHIR - 2.5. Thanks to Gary Graham for the pull request! - - - Add a missing @Deprecated tag. Thanks to Drew Mitchell for the pull request! - - - The JSON parser has removed a few unneeded super keywords that prevented overriding behaviour. - Thanks to Anders Havn for the pull request! - - - The DSTU2/3 version converter now converts Specimen resources. Thanks to Gary - Graham for the pull request! - -
        - - - HAPI FHIR is now built using OpenJDK 11. Users are recommended to upgrade to this version - of Java if this is feasible. We are not yet dropping support for Java 8 (aka 1.8), but - users are recommended to upgrade if possible. - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Spring (JPA): 5.0.8.RELEASE -> 5.1.3.RELEASE
      • -
      • Spring-Data (JPA): 2.0.7.RELEASE -> 2.1.3.RELEASE
      • -
      • Hibernate-Core (JPA): 5.3.6.FINAL -> 5.4.1.FINAL
      • -
      • Hibernate-Search (JPA): 5.10.3.FINAL -> 5.11.1.FINAL
      • -
      • Thymeleaf (JPA): 3.0.9.RELEASE -> 3.0.11.RELEASE
      • -
      • thymeleaf-spring4 (Testpage Overlay) has been replaced with thymeleaf-spring5
      • -
      • Commons-Lang3: 3.8 -> 3.8.1
      • -
      • Commons-Text: 1.4 -> 1.4
      • -
      • Spring Boot: 1.5.6.RELEASE -> 2.1.1.RELEASE
      • - - ]]> -
        - - Changed subscription processing, if the subscription criteria are straightforward (i.e. no - chained references, qualifiers or prefixes) then attempt to match the incoming resource against - the criteria in-memory. If the subscription criteria can't be matched in-memory, then the - server falls back to the original subscription matching process of querying the database. The - in-memory matcher can be disabled by setting isEnableInMemorySubscriptionMatching to "false" in - DaoConfig (by default it is true). If isEnableInMemorySubscriptionMatching is "false", then all - subscription matching will query the database as before. - - - Removed BaseSubscriptionInterceptor and all its subclasses (RestHook, EMail, WebSocket). These are replaced - by two new interceptors: SubscriptionActivatingInterceptor that is responsible for activating subscriptions - and SubscriptionMatchingInterceptor that is responsible for matching incoming resources against activated - subscriptions. Call DaoConfig.addSupportedSubscriptionType(type) to configure which subscription types - are supported in your environment. If you are processing subscriptions on a separate server and only want - to activate subscriptions on this server, you should set DaoConfig.setSubscriptionMatchingEnabled to false. - The helper method SubscriptionInterceptorLoader.registerInterceptors() - will check if any subscription types are supported, and if so then load active subscriptions into the - SubscriptionRegistry and register the subscription activating interceptor. This method also registers - the subscription matching interceptor (that matches incoming resources and sends matches to subscription - channels) only if DaoConfig.isSubscriptionMatchingEnabled is true. - See https://github.com/jamesagnew/hapi-fhir/wiki/Proposed-Subscription-Design-Change for more - details. - - - Added support for matching subscriptions in a separate server from the REST Server. To do this, run the - SubscriptionActivatingInterceptor on the REST server and the SubscriptionMatchingInterceptor in the - standalone server. Classes required to support running a standalone subscription server are in the - ca.uhn.fhir.jpa.subscription.module.standalone package. These classes are excluded by default from - the JPA ApplicationContext (that package is explicitly filtered out in the BaseConfig.java @ComponentScan). - - - Changed behaviour of FHIR Server to reject subscriptions with invalid criteria. If a Subscription - is submitted with invalid criteria, the server returns HTTP 422 "Unprocessable Entity" and the - Subscription is not persisted. - - - The JPA server $expunge operation could sometimes fail to expunge if - another resource linked to a resource that was being - expunged. This has been corrected. In addition, the $expunge operation - has been refactored to use smaller chunks of work - within a single DB transaction. This improves performance and reduces contention when - performing large expunge workloads. - - - A badly formatted log message when handing exceptions was cleaned up. Thanks to - Magnus Watn for the pull request! - - - A NullPointerException has been fixed when using custom resource classes that - have a @Block class as a child element. Thanks to Lars Gram Mathiasen for - reporting and providing a test case! - - - AuthorizationInterceptor now allows the GraphQL operation to be - authorized. Note that this is an all-or-nothing grant for now, it - is not yet possible to specify individual resource security when - using GraphQL. - - - The ResponseHighlighterInterceptor now declines to handle Binary responses - provided as a response from extended operations. In other words if the - operation $foo returns a Binary resource, the ResponseHighliterInterceptor will - not provide syntax highlighting on the response. This was previously the case for - the /Binary endpoint, but not for other binary responses. - - - FHIR Parser now has an additional overload of the - parseResource]]> method that accepts - an InputStream instead of a Reader as the source. - - - FHIR Fluent/Generic Client now has a new return option called - returnMethodOutcome]]> which can be - used to return a raw response. This is handy for invoking operations - that might return arbitrary binary content. - - - Moved state and functionality out of BaseHapiFhirDao.java into new classes: LogicalReferenceHelper, - ResourceIndexedSearchParams, IdHelperService, SearcchParamExtractorService, and MatchUrlService. - - - Replaced explicit @Bean construction in BaseConfig.java with @ComponentScan. Beans with state are annotated - with - @Component and stateless beans are annotated as @Service. Also changed SearchBuilder.java and the - three Subscriber classes into @Scope("protoype") so their dependencies can be @Autowired injected - as opposed to constructor parameters. - - - A bug in the JPA resource reindexer was fixed: In many cases the reindexer would - mark reindexing jobs as deleted before they had actually completed, leading to - some resources not actually being reindexed. - - - The JPA stale search deletion service now deletes cached search results in much - larger batches (20000 instead of 500) in order to reduce the amount of noise - in the logs. - - - AuthorizationInterceptor now allows arbitrary FHIR $operations to be authorized, - including support for either allowing the operation response to proceed unchallenged, - or authorizing the contents of the response. - - - JPA Migrator tool enhancements: - An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against - Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL - statements will be logged at the end of the run. Also, a case sensitivity issue when running against - some Postgres databases has been corrected. - - - In the JPA server, when performing a chained reference search on a search parameter with - a target type of - Reference(Any)]]>, the search failed with an incomprehensible - error. This has been corrected to return an error message indicating that the chain - must be qualified with a resource type for such a field. For example, - QuestionnaireResponse?subject:Patient.name=smith]]> - instead of - QuestionnaireResponse?subject.name=smith]]>. - - - The LOINC uploader has been updated to suport the LOINC 2.65 release - file format. - - - The resource reindexer can now detect when a resource's current version no longer - exists in the database (e.g. because it was manually expunged), and can automatically - adjust the most recent version to - account for this. - - - When updating existing resources, the JPA server will now attempt to reuse/update - rows in the index tables if one row is being removed and one row is being added (e.g. - because a Patient's name is changing from "A" to "B"). This has the net effect - of reducing the number - - - An issue was corrected with the JPA reindexer, where String index columns do not always - get reindexed if they did not have an identity hash value in the HASH_IDENTITY column. - - - Plain Server ResourceProvider classes are no longer required to be public classes. This - limitation has always been enforced, but did not actually serve any real purpose so it - has been removed. - - - A new interceptor called ServeMediaResourceRawInterceptor has been added. This interceptor - causes Media resources to be served as raw content if the client explicitly requests - the correct content type cia the Accept header. - - - A new configuration item has been added to the FhirInstanceValidator that - allows you to specify additional "known extension domains", meaning - domains in which the validator will not complain about when it - encounters new extensions. Thanks to Heinz-Dieter Conradi for the - pull request! - - - Under some circumstances, when a custom search parameter was added to the JPA server - resources could start reindexing before the new search parameter had been saved, meaning that - it was not applied to all resources. This has been corrected. - - - In example-projects/README.md and hapi-fhir-jpaserver-example/README.md, incidate that these examples - projects - are no longer maintained. The README.md points users to a starter project they should use for examples. - - - Replaced use of BeanFactory with custom factory classes that Spring @Lookup the @Scope("prototype") beans - (e.g. SearchBuilderFactory). - - - Moved e-mail from address configuration from EmailInterceptor (which doesn't exist any more) to DaoConfig. - - - Added 3 interfaces for services required by the standalone subscription server. The standalone subscription - server doesn't have access to a database and so needs to get its resources using a FhirClient. Thus - for each of these interfaces, there are two implementations: a Dao implementaiton and a FhirClient - implementation. The interfaces thus introduced are ISubscriptionProvider (used to load subscriptions - into the SubscriptionRegistry), the IResourceProvider (used to get the latest version of a resource - if the "get latest version" flag is set on the subscription) and ISearchParamProvider used to load - custom search parameters. - - - Separated active subscription cache from the interceptors into a new Spring component called the - SubscriptionRegistry. This component maintains a cache of ActiveSubscriptions. An ActiveSubscription - contains the subscription, it's delivery channel, and a list of delivery handlers. - - - Introduced a new Spring factory interface ISubscribableChannelFactory that is used to create delivery - channels and handlers. By default, HAPI FHIR ships with a LinkedBlockingQueue implementation of the - delivery channel factory. If a different type of channel factory is required (e.g. JMS or Kafka), add it - to your application context and mark it as @Primary. - - - When using the HL7.org DSTU2 structures, a QuestionnaireResponse with a - value of type reference would fail to parse. Thanks to David Gileadi for - the pull request! - - - FHIR Servers now support the HTTP HEAD method for FHIR read operations. Thanks - to GitHub user Cory00 for the pull request! - - - When running the JPA server on Oracle, certain search queries that return a very large number of - _included resources failed with an SQL exception stating that too many parameters were used. Search - include logic has been reworked to avoid this. - - - JPA Subscription deliveries did not always include the accurate versionId if the Subscription - module was configured to use an external queuing engine. This has been corrected. - - - It is now possible in a plain or JPA server to specify the default return - type for create/update operations when no Prefer header has been provided - by the client. - - - It is now possible in a JPA server to specify the _total calculation - behaviour if no parameter is supplied by the client. This is done using a - new setting on the DaoConfig. This can be used to force a total to - always be calculated for searches, including large ones. - - - AuthorizationInterceptor now rejects transactions with an invalid or unset request - using an HTTP 422 response Bundle type instead of silently refusing to authorize them. - - - AuthorizationInterceptor is now able to authorize DELETE operations performed via a - transaction operation. Previously these were always denied. - - - OperationDefinitions are now created for named queries in server - module. Thanks to Stig Døssing for the pull request! - - - A new server interceptor has been added called "SearchNarrowingInterceptor". - This interceptor can be used to automatically narrow the scope of searches - performed by the user to limit them to specific resources or compartments - that the user should have access to. - - - In a DSTU2 server, if search parameters are expressed with chains directly in the - parameter name (e.g. - @RequiredParam(name="subject.name.family")]]>) the second - part of the chain was lost when the chain was described in the server - CapabilityStatement. This has been corrected. - - - In the JPA server, search/read operations being performed within a transaction bundle - did not pass the client request HTTP headers to the sub-request. This meant that - AuthorizationInterceptor could not authorize these requests if it was depending on - headers being present. - - - When using a client in DSTU3/R4 mode, if the client attempted to validate the server - CapabilityStatement but was not able to parse the response, the client would throw - an exception with a misleading error about the Conformance resource not existing. This - has been corrected. Thanks to Shayaan Munshi for reporting and providing a test case! - - - It is now possible to upload a ConceptMap to the JPA server containing mappings where the - source or target is a StructureDefinition canonical URI. This was previously blocked, as the - system could not apply these mappings. It is now permitted to be stored, although - the system will still not apply these mappings. - - - A wrapper script for Maven has been added, enabling new users to use Maven without having - to install it beforehand. Thanks to Ari Ruotsalainen for the Pull Request! - - - AuthorizationInterceptor can now allow a user to perform a search that is scoped to a particular - resource (e.g. Patient?_id=123) if the user has read access for that specific instance. - - - In JPA Server REST Hook Subscriptions, any Headers defined in the - Subscription resource are now applied to the outgoing HTTP - request. - Thanks to Volker Schmidt for the pull request! - - - HAPI FHIR will now log the Git revision when it first starts up (on the ame line as the version number - that it already logs). - - - When fetching a page of search results, if a page offset beyond the total number - of available result was requested, a single result was still returned (e.g. - requesting a page beginning at index 1000 when there are only 10 results would - result in the 10th result being returned). This will now result in an empty - response Bundle as would be expected. - - - Added support for _id in in-memory matcher - - - The casing of the base64Binary datatype was incorrect in the DSTU3 and R4 model classes. - This has been corrected. - - - Add a "subscription-matching-strategy" meta tag to incoming subscriptions with value of IN_MEMORY - or DATABASE indicating whether the subscription can be matched against new resources in-memory or - whether a call out to the database may be required. I say "may" because subscription matches fail fast - so a negative match may be performed in-memory, but a positive match will require a database call. - - - When performing a JPA search with a chained :text modifier - (e.g. MedicationStatement?medication.code:text=aspirin,tylenol) a series - of unneccesary joins were introduced to the generated SQL query, harming - performance. This has been fixed. - - - A serialization error when performing some searches in the JPA server - using data parameters has been fixed. Thanks to GitHub user - @PickOneFish for reporting! - - - An issue with outdated syntax in the Vagrant file that prevent it from being used - was corrected. Thanks to Steve Lewis for the pull requst! - - - The HAPI FHIR tutorial server project had outdated versions of HAPI FHIR - in its pom file. Thanks to Ricardo Estevez for the pull request! - - - A NullPointerException during validation was fixed. Thanks to GitHub - user zilin375 for the pull request! - - - Support for validating enableWhen in Questionnaires has been added to the Validator. Thanks - to Eeva Turkka and Matti Uutsitalo for the pull request! - -
        - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Karaf (OSGi): 4.1.4 -> 4.1.6
      • -
      • Commons-Compress (JPA): 1.14 -> 1.18
      • -
      • Jackson (JPA): 2.9.2 -> 2.9.7
      • - - ]]> -
        - - A bug in the JPA migration tasks from 3.4.0 to 3.5.0 caused a failure if the HFJ_SEARCH_PARM - table did not exist. This table existed in previous versions of HAPI FHIR but was dropped - in 3.5.0, meaning that migrations would fail if the database was created using a snapshot - version of 3.5.0. - - - Automatic ID generation for contained resources (in cases where the user hasn't manually specified an ID) - has been streamlined to generate more predictable IDs in some cases. - - - An issue in the HAPI FHIR CLI database migrator command has been resolved, where - some database drivers did not automatically register and had to be manually added to - the classpath. - - - The module which deletes stale searches has been modified so that it deletes very large - searches (searches with 10000+ results in the query cache) in smaller batches, in order - to avoid having very long running delete operations occupying database connections for a - long time or timing out. - - - When invoking an operation using the fluent client on an instance, the operation would - accidentally invoke against the server if the provided ID did not include a type. This - has been corrected so that an IllegalArgumentException is now thrown. - - - A new operation has been added to the JPA server called - $trigger-subscription]]>. This can - be used to cause a transaction to redeliver a resource that previously triggered. - See - this link]]> - for a description of how this feature works. Note that you must add the - SubscriptionRetriggeringProvider as shown in the sample project - here.]]> - - - When operating in R4 mode, the HAPI FHIR server will now populate Bundle.entry.response - for history and search results, which is did not previously do. - - - The JPA database migrator tool has been enhanced so that it now supports migrations from - HAPI FHIR 3.3.0 to HAPI FHIR 3.4.0 / 3.5.0+ as well. - - - When using the HAPI FHIR CLI, user-prompted passwords were not correctly encoded, meaning that the - "--basic-auth PROMPT" action was not usable. This has been corrected. - - - The JPA server SearchCoordinator has been refactored to make searches more efficient: - When a search is performed, the SearchCoordinator loads multiple pages of results even - if the user has only requested a small number. This is done in order to prevent needing - to re-run the search for every page of results that is loaded. - In previous versions of HAPI FHIR, when a search was made the SearchCoordinator would - prefetch as many results as the user could possibly request across all pages (even if - this meant prefetching thousands or millions of resources). - As of this version, a new option has been added to DaoConfig that specifies how many - resources to prefetch. This can have a significant impact on performance for servers - with a large number of resources, where users often only want the first page - of results. - See - DatConfig#setSearchPreFetchThresholds()]]> - for configuration of this feature. - - - When performing a JPA server using a date parameter, if a time is not specified in - the query URL, the date range is expanded slightly to include all possible - timezones where the date that could apply. This makes the search slightly more - inclusive, which errs on the side of caution. - - - A bug was fixed in the JPA server $expunge operation where a database connection - could sometimes be opened and not returned to the pool immediately, leading to - pool starvation if the operation was called many times in a row. - - - A new setting has been added to the JPA server DaoConfig that causes the server - to keep certain searches "warm" in the cache. This means that the search will - be performed periodically in the background in order to keep a reasonably fresh copy - of the results in the query cache. - - - When using the testpage overlay to delete a resource, currently a crash can occur - if an unqualified ID is placed in the ID text box. This has been corrected. - - - AuthorizationInterceptor did not allow FHIR batch operations when the transaction() - permission is granted. This has been corrected so that transaction() allows both - batch and transaction requests to proceed. - - - The JPA server now automatically supplies several appropriate hibernate performance - settings as long as the JPA EntityManagerFactory was created using HAPI FHIR's - built-in method for creating it. -
        ]]> - Existing JPA projects should consider using - super.entityManagerFactory()]]> - as shown in - the example project]]> - if they are not already. -
        - - The FhirTerser getValues(...)]]> methods have been overloaded. The terser can now be - used to create a null-valued element where none exists. Additionally, the terser can now add a null-valued - extension where one or more such extensions already exist. These changes allow better population of FHIR - elements provided an arbitrary FHIR path. - - - The FhirTerser getValues(...)]]> methods were not properly handling modifier - extensions for verions of FHIR prior to DSTU3. This has been corrected. - - - When updating resources in the JPA server, a bug caused index table entries to be refreshed - sometimes even though the index value hadn't changed. This issue did not cause incorrect search - results but had an effect on write performance. This has been corrected. - - - The @Operation annotation used to declare operations on the Plain Server now - has a wildcard constant which may be used for the operation name. This allows - you to create a server that supports operations that are not known to the - server when it starts up. This is generally not advisable but can be useful - for some circumstances. - - - When using an @Operation method in the Plain Server, it is now possible - to use a parameter annotated with @ResourceParam to receive the Parameters - (or other) resource supplied by the client as the request body. - - - The JPA server version migrator tool now runs in a multithreaded way, allowing it to - upgrade th database faster when migration tasks require data updates. - - - A bug in the JPA server was fixed: When a resource was previously deleted, - a transaction could not be posted that both restored the deleted resource but - also contained references to the now-restored resource. - - - The $expunge operation could sometimes fail to delete resources if a resource - to be deleted had recently been returned in a search result. This has been - corrected. - - - A new setting has been added to the JPA Server DopConfig that controls the - behaviour when a client-assigned ID is encountered (i.e. the client performs - an HTTP PUT to a resource ID that doesn't already exist on the server). It is - now possible to disallow this action, to only allow alphanumeric IDs (the default - and only option previously) or allow any IDs including alphanumeric. - - - It is now possible to use your own IMessageResolver instance in the narrative - generator. Thanks to Ruth Alkema for the pull request! - - - When restful reponses tried to return multiple instances of the same response header, - some instances were discarded. Thanks to Volker Schmidt for the pull request! - - - The REST client now allows for configurable behaviour as to whether a - _format]]> - parameter should be included in requests. - - - JPA server R4 SearchParameter custom expression validation is now done using the - actual FHIRPath evaluator, meaning it is more rigorous in what it can find. - - - A NullPointerException in DateRangeParam when a client URL conrtained a malformed - date was corrected. Thanks Heinz-Dieter Conradi for the Pull Request! - -
        - - - HAPI FHIR now supports JDK 9 and JDK 10, both for building HAPI FHIR - as well as for use. JDK 8 remains supported and is the minimum requirement - in order to build or use HAPI FHIR. - - - A new command has been added to the HAPI FHIR CLI tool: "migrate-database". This - command performs the schema modifications required when upgrading HAPI FHIR JPA - to a new version (previously this was a manual process involving running scripts and - reindexing everything). -
        - See the - command documentation - for more information on how to use this tool. Please post in the HAPI FHIR - Google Group if you run into issues, as this is a brand new framework and we still need - lots of help with testing. - ]]> -
        - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Gson (JSON Parser): 2.8.1 -> 2.8.5
      • -
      • Spring Framework (JPA): 5.0.3.RELEASE -> 5.0.8.RELEASE
      • -
      • Hibernate ORM (JPA): 5.2.16.Final -> 5.3.6.Final
      • -
      • Hibernate Search (JPA): 5.7.1.Final -> 5.10.3.Final
      • -
      • Jetty (CLI): 9.4.8.v20171121 -> 9.4.12.v20180830
      • -
      • Commons-Codec (All): 1.10 -> 1.11
      • -
      • Commons-Lang (All): 3.7 -> 3.8
      • -
      • Commons-IO (All): 2.5 -> 2.6
      • -
      • Spring-Data (JPA): 1.11.6.RELEASE -> 2.0.7.RELEASE
      • - - ]]> -
        - - A new mnandatory library depdendency has been added to hapi-fhir-base, meaning that all - applications using HAPI FHIR must import ti: commons-text. This library has been added as - a few utility methods used by HAPI FHIR that were formerly in the commons-lang3 - project have been moved into commons-text. This library has been added as a non-optional - dependency in the hapi-fhir-base POM, so Maven/Gradle users should not have to make - any changes. - - - The JPA server now has a configuration item in the DaoConfig to specify which bundle types - may be stored as-is on the /Bundle endpoint. By default the following types - are allowed: collection, document, message. - - - CapabilityStatements generated by the server module will now include the server - base URL in the - CapabilityStatement.implementation.url]]> - field. - - - Spring-data (used by the JPA server) has been upgraded to version 2.0.7 - (from version 1.11.6). Thanks to Roman Doboni for the pull request! - - - A crash in the validator was fixed: Validating a Bundle that did not have Bundle.fullUrl - populated could cause a NullPointerException. - - - AuthorizationInterceptor now examines requests more closely in order - to block requests early that are not possibly going to return - allowable results when compartment rules are used. For example, - if an AuthorizationInterceptor is configured to allow only - read]]> - access to compartment - Patient/123]]>, - a search for - Observation?subject=987]]> - will now be blocked before the method handler is called. Previously - the search was performed and the results were examined in order to - determine whether they were all in the appropriate compartment, but - this incurs a performance cost, and means that this search would - successfully return an empty Bundle if no matches were present. -
        ]]> - A new setting on AuthorizationInterceptor called - setFlags(flags)]]> - can be used to maintain the previous behaviour. -
        - - JPA server loading logic has been improved to enhance performance when - loading a large number of results in a page, or when loading multiple - search results with tags. Thanks to Frank Tao for the pull request! - This change was introduced as a part of a collaboration between - HAPI FHIR and the US National Institiutes for Health (NIH). - - - Resource loading logic for the JPA server has been optimized to - reduce the number of database round trips required when loading - search results where many of the entries have a "forced ID" (an alphanumeric - client-assigned resource ID). Thanks to Frank Tao for the pull - request! - This change was introduced as a part of a collaboration between - HAPI FHIR and the US National Institiutes for Health (NIH). - - - LOINC uploader has been updated to support the new LOINC filename - scheme introduced in LOINC 2.64. Thanks to Rob Hausam for the - pull request! - - - In the JPA server, it is now possible for a custom search parameter - to use the - resolve()]]> function in its path to descend into - contained resources and index fields within them. - - - A new IValidationSupport implementation has been added, named CachingValidationSupport. This - module wraps another implementation and provides short-term caching. This can have a dramatic - performance improvement on servers that are validating or executing FHIRPath repeatedly - under load. This module is used by default in the JPA server. - - - An index in the JPA server on the HFJ_FORCED_ID table was incorrectly - not marked as unique. This meant that under heavy load it was possible to - create two resources with the same client-assigned ID. - - - The JPA server - $expunge]]> - operation deleted components of an individual resource record in - separate database transactions, meaning that if an operation failed - unexpectedly resources could be left in a weird state. This has been - corrected. - - - A bug was fixed in the JPA terminology uploader, where it was possible - in some cases for some ValueSets and ConceptMaps to not be saved because - of a premature short circuit during deferred uploading. Thanks to - Joel Schneider for the pull request! - - - A bug in the HAPI FHIR CLI was fixed, where uploading terminology for R4 - could cause an error about the incorrect FHIR version. Thanks to - Rob Hausam for the pull request! - - - A new method has been added to AuthorizationInterceptor that can be used to - create rules allowing FHIR patch operations. See - Authorizing Patch Operations]]> - for more information. - - - A new view has been added to the JPA server, reducing the number of database - calls required when reading resources back. This causes an improvement in performance. - Thanks to Frank Tao for the pull request! - - - A crash was fixed when deleting a ConceptMap resource in the - JPA server. This crash was a regression in HAPI FHIR 3.4.0. - - - A crash in the JPA server when performing a manual reindex of a deleted resource - was fixed. - - - Using the generic/fluent client, it is now possible to - invoke the $process-message method using a standard - client.operation() call. Previously this caused a strange - NullPointerException. - - - The REST Server now sanitizes URL path components and query parameter - names to escape several reserved characters (e.g. " and <) - in order to prevent HTML injection attacks via maliciously - crafted URLs. - - - The generic/fluent client now supports the :contains modifier on - string search params. Thanks to Clayton Bodendein for the pull - request! - - - The HAPI FHIR Server has been updated to correctly reflect the current - FHIR specification behaviour for the Prefer header. This means that - the server will no longer return an OperationOutcome by default, but - that one may be requested via a Prefer header, using the newly implemented - "Repreentation: OperationOutcome" value. - Thanks to Ana Maria Radu for the pul request! - - - The REST Server module now allows more than one Resource Provider - (i.e. more than one implementation of IResourceProvider) to be registered - to the RestfulServer for the same resource type. Previous versions of - HAPI FHIR have always limited support to a single resource provider, but - this limitation did not serve any purpose so it has been removed. - - - The HashMapResourceProvider now supports the type and - instance history operations. In addition, the search method - for the - _id]]> search parameter now has the - search parameter marked as "required". This means that additional - search methods can be added in subclasses without their intended - searches being routed to the searchById method. Also, the resource - map now uses a LinkedHashMap, so searches return a predictable - order for unit tests. - - - Fixed a bug when creating a custom search parameter in the JPA - server: if the SearchParameter resource contained an invalid - expression, create/update operations for the given resource would - fail with a cryptic error. SearchParameter expressions are now - validated upon storage, and the SearchParameter will be rejected - if the expression can not be processed. - - - The generic client history operations (history-instance, history-type, - and history-server) now support the - _at]]> parameter. - - - In the plain server, many resource provider method parameters may now - use a generic - IPrimitiveType<String>]]> - or - IPrimitiveType<Date>]]> at the - parameter type. This is handy if you are trying to write code - that works across versions of FHIR. - - - Several convenience methods have been added to the fluent/generic - client interfaces. These methods allow the adding of a sort via a - SortSpec object, as well as specifying search parameters via a plain - Map of Strings. - - - A new client interceptor called ThreadLocalCapturingInterceptor has been - added. This interceptor works the same way as CapturingInterceptor in that - it captures requests and responses for later processing, but it uses - a ThreadLocal object to store them in order to facilitate - use in multithreaded environments. - - - A new constructor has been added to the client BasicAuthInterceptor - allowing credentials to be specified in the form - "username:password" as an alternate to specifying them as two - discrete strings. - - - SimpleBundleProvider has been modified to optionally allow calling - code to specify a search UUID, and a field to allow the preferred - page size to be configured. - - - The JPA server search UUID column has been reduced in length from - 40 chars to 36, in order to align with the actual length of the - generated UUIDs. - - - Plain servers using paging may now specify an ID/name for - individual pages being returned, avoiding the need to - respond to arbitrary offset/index requests from the server. - In this mode, page links in search result bundles simply - include the ID to the next page. - - - An issue was fixed in BundleUtil#toListOfEntries, where sometimes - a resource could be associated with the wrong entry in the response. - Thanks to GitHub user @jbalbien for the pull request! - - - JPA subscription delivery queues no longer store the resource body in the - queue (only the ID), which should reduce the memory/disk footprint of the queue - when it grows long. - - - A bug was fixed in JPA server searches: When performing a search with a _lastUpdate - filter, the filter was applied to any _include values, which it should not have been. - Thanks to Deepak Garg for reporting! - - - When performing a ConceptMap/$translate operation with reverse="true" in the arguments, - the equivalency flag is now set on the response just as it is for a non-reverse lookup. - - - When executing a FHIR transaction in JPA server, if the request bundle contains - placeholder IDs references (i.e. "urn:uuid:*" references) that can not be resolved - anywhere else in the bundle, a user friendly error is now returned. Previously, - a cryptic error containing only the UUID was returned. As a part of this change, - transaction processing has now been consolidated into a single codebase for DSTU3 - / R4 (and future) versions of FHIR. This should greatly improve maintainability - and consistency for transaction processing. - - - ResponseHighlighterInterceptor now displays the total size of the output and - an estimate of the transfer time at the bottom of the response. - - - The Prefer header is now honoured for HTTP PATCH requests. Thanks to - Alin Leonard for the Pull Request! - - - The Composition]]> operation $document]]> has been - implemented. Thanks to Patrick Werner for the Pull Request! - - - HAPI FHIR CLI commands that allow Basic Auth credentials or a Bearer Token may now use - a value of "PROMPT" to cause the CLI to prompt the user for credentials using an - interactive prompt. - - - The experimental "dynamic mode" for search parameter registration has been removed. This - mode was never published or documented and was labelled as experimental, so I am hoping it - was never depended on by anyone. Please post on the HAPI FHIR mailing list if this - change affects you. - - - A crash was fixed when using the ConceptMap/$translate operation to translate a mapping - where the equivalence was not specified. - - - The maximum length for codes in the JPA server terminology service have been increased - to 500 in order to better accomodate code systems with very long codes. - - - A bug in the DSTU3 validator was fixed where validation resources such as StructureDefinitions - and Questionnaires were cached in a cache that never expired, leading to validations against - stale versions of resources. - - - In the REST server, if an incoming request has the Content-Encoding header, the server will - not try to read request parameters from the content stream. This avoids an incompatibility with - new versions of Jetty. - - - Custom profile names when not matching standard FHIR profile names, are now - handled properly by the validator. Thanks to Anthony Sute - for the Pull Request! - - - The JPA server now performs a count query instead of a more expensive data query - when searches using - _summary=count]]>. - This means that a total will always be returned in the Bundle (this isn't always - guaranteed otherwise, since the Search Controller can result in data being returned - before the total number of results is known). - - - The JPA server SearchCoordinator now prefetches only a smaller and configurable number - of results during the initial search request, and more may be requested in subsequent - page requests. This change may have a significant improvement on performance: in - previous versions of HAPI FHIR, even if the user only wanted the first page of 10 - results, many many more might be prefetched, consuming database resources and - server time. - -
        - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Commons-Lang3 (All): 3.6 -> 3.7
      • -
      • Hibernate (JPA): 5.2.12.Final -> 5.2.16.Final
      • -
      • Javassist (JPA): 3.20.0-GA -> 3.22.0-GA
      • - - ]]> -
        - - Several enhancements have been made to the JPA server index - tables. These enhancements consist of new colums that will be - used in a future version of HAPI FHIR to significantly decrease - the amount of space required for indexes on token and string index - types. -
        ]]> - These new columns are not yet used in HAPI FHIR 3.4.0 but will be - enabled in HAPI FHIR 3.5.0. Anyone upgrading to HAPI FHIR 3.4.0 (or above) - is recommended to invoke the following SQL statement on their - database in order to reindex all data in a background job: - ]]> - update HFJ_RESOURCE set SP_INDEX_STATUS = null;]]> - ]]> - Note that if you do this reindex now, you will not have any downtime while - you upgrade to HAPI FHIR 3.5.0. If you need to perform the reindex at the - time that you upgrade to HAPI FHIR 3.5.0 some indexes may not be - available. - ]]> - In addition, the following schema changes should be made while upgrading: - ]]> - update table TRM_CODESYSTEM_VER drop column RES_VERSION_ID;
        - alter table TRM_CODESYSTEM_VER drop constraint IDX_CSV_RESOURCEPID_AND_VER
        ]]> -
        - - R4 structures have been updated to the latest definitions - (SVN 13732) - - - When calling a getter on a DSTU3/R4 structure for a choice type - (e.g. Observation#getValueString()), a NullPointerException - was thrown if there was no value in this field, and the NPE - had no useful error message. Now this method call will simply - return null. - method - - - When performing a FHIR resource update in the JPA server - where the update happens within a transaction, and the - resource being updated contains placeholder IDs, and - the resource has not actually changed, a new version was - created even though there was not actually any change. - This particular combination of circumstances seems very - specific and improbable, but it is quite common for some - types of solutions (e.g. mapping HL7v2 data) so this - fix can prevent significant wasted space in some cases. - - - JPA server index tables did not have a column length specified - on the resource type column. This caused the default of 255 to - be used, which wasted a lot of space since resource names are all - less than 30 chars long and a single resource can have 10-100+ - index rows depending on configuration. This has now been set - to a much more sensible 30. - - - The LOINC uploader for the JPA Terminology Server has been - significantly beefed up so that it now takes in the full - set of LOINC distribution artifacts, and creates not only - the LOINC CodeSystem but a complete set of concept properties, - a number of LOINC ValueSets, and a number of LOINC ConceptMaps. - This work was sponsored by the Regenstrief Institute. Thanks - to Regenstrief for their support! - - - The DSTU2 validator has been refactored to use the same codebase - as the DSTU3/R4 validator (which were harmonized in HAPI FHIR 3.3.0). - This means that we now have a single codebase for all validators, which - improves maintainability and brings a number of improvements - to the accuracy of DSTU2 resource validation. - - - When encoding a resource that had contained resources with user-supplied - local IDs (e.g. resource.setId("#1")) as well as contained resources - with no IDs (meaning HAPI should automatically assign a local ID - for these resources) it was possible for HAPI to generate - a local ID that already existed, making the resulting - serialization invalid. This has been corrected. - - - The REST Generic Client now supports invoking an operation - on a specific version of a resource instance. - - - A new operation has been added to the JPA server called - "$expunge". This operation can be used to physically delete - old versions of resources, logically deleted resources, or - even all resources in the database. - - - An experimental new feature has been added to AuthorizationInterceptor which - allows user-supplied checkers to add additional checking logic - to determine whether a particular rule applies. This could be - used for example to restrict an auth rule to particular - source IPs, or to only allow operations with specific - parameter values. - - - A new qualifier has been added to the AuthorizationInterceptor - RuleBuilder that allows a rule on an operation to match - atAnyLevel()]]>, meaning that the rule - applies to the operation by name whether it is at the - server, type, or instance level. - - - Calling IdType#withVersion(String)]]> - with a null/blank parameter will now return a copy of the - ID with the version removed. Previously this call would - deliberately cause an IllegalArgumentException. - - - When updating resources on the JPA server, tags did not always - consistently follow FHIR's recommended rules for tag retention. According - to FHIR's rules, if a tag is not explicitly present on an update but - was present on the previous version, it should be carried forward anyhow. - Due to a bug, this happened when more than one tag was present - but not when only one was present. This has been corrected. In - addition, a new request header called - X-Meta-Snapshot-Mode]]> - has been added that can be used by the client to override - this behaviour. - - - The JPA server's resource counts query has been optimized to - give the database a bit more flexibility to - optimize, which should increase performance for this query. - - - The JPA server CapabilityStatement generator has been tuned - so that resource counts are no longer calculated synchronously - as a part of building the CapabilityStatement response. With - this change, counts are calculated in the background and cached - which can yield significant performance improvements on - hevaily loaded servers. - - - Fix a significant performance regression in 3.3.0 when validating DSTU3 content using the - InstanceValidator. From 3.3.0 onward, StructureDefinitions are converted to FHIR R4 - content on the fly in order to reduct duplication in the codebase. These conversions - happened upon every validation however, instead of only happening once which adversely - affected performance. A cache has been added. - - - Fix a bug in the DSTU2 QuestionnaireResponseValidator which prevented validation - on groups with only one question. Thanks David Gileadi for the pull request! - - - The ConceptMap]]> operation $translate]]> has been - implemented. - - - HAPI-FHIR_CLI now includes two new commands: one for importing and populating a - ConceptMap]]> resource from a CSV; and one for exporting a - ConceptMap]]> resource to a CSV. - - - Operation methods on a plain server may now use parameters - of type String (i.e. plain Java strings), and any FHIR primitive - datatype will be automatically coerced into a String. - - - The HAPI FHIR CLI now supports importing an IGPack file as an import - to the validation process. - - - When two threads attempt to update the same resource at the same time, previously - an unspecified error was thrown by the JPA server. An HTTP 409 - (Conflict) with an informative error message is now thrown. - - - A bug in the JPA server's DSTU2 transaction processing routine caused it - to occasionally consume two database connections, which could lead to deadlocks - under heavy load. This has been fixed. - - - AuthorizationInterceptor sometimes incorrectly identified an operation - invocation at the type level as being at the instance level if the method - indicated that the IdParam parameter was optional. This has been fixed. - - - StructureDefinitions for the FHIR standard extensions have been added to the - hapi-fhir-validation-resources-XXXX modules. Thanks to Patrick Werner for the - pull request! These have also been added to the list of definitions uploaded - by the CLI "upload-definitions" command. - - - A workaround for an invalid search parameter path in the R4 consent - resource has been implemented. This path was preventing some Consent - resources from successfully being uploaded to the JPA server. Thanks to - Anthony Sute for identifying this. - - - A hard-to-understand validation message was fixed in the validator when - validating against profiles that declare some elements as mustSupport - but have others used but not declared as mustSupport. Thanks to Patrick - Werner for the PR! - - - The HAPI FHIR CLI is now available for installation on OSX using the - (really excellent) Homebrew package manager thanks to an effort by - John Grimes to get it added. Thanks John! - - - When the REST Server experiences an expected error (such as a NullPointerException) - in a resource provider class, a simple message of "Failed to call access method" is - returned to the user. This has been enhanced to also include the message from - the underlying exception. - - - A bug in the plain server was fixed that prevented some includes from - correctly causing their targets to be included in the response bundle. - Thanks to GitHub user @RuthAlk for the pull request! - - - DateRangeParameter was enhanced to support convenient method chanining, and - the parameter validation was improved to only change state after validating - that parameters were valid. Thanks to Gaetano Gallo for the pull request! - - - The HumanName DSTU3+ datatype had convenience methods for testing - whether the name has a specific given name or not, but these methods - did not work. Thanks to Jason Owen for reporting and providing a test - case! - - - An issue was corrected in the validator where Questionnaire references that - used contained resources caused an unexpected crash. Thanks to - Heinz-Dieter Conradi for the pull request! - - - An issue in the narrative generator template for the CodeableConcept - datatype was corrected. Thanks to @RuthAlk for the pull request! - - - The JPA server automatic reindexing process has been tweaked so that it no - longer runs once per minute (this was a heavy strain on large databases) - but will instead run once an hour unless triggered for some reason. In addition, - the number of threads allocated to reindexing may now be adjusted via a - setting in the DaoConfig. - - - AuthorizationInterceptor did not correctly grant access to resources - by compartment when the reference on the target resource that pointed - to the compartment owner was defined using a resource object (ResourceReference#setResource) - instead of a reference (ResourceReference#setReference). - - - Several tests were added to ensure accurate validation of QuestionnaireResponse - resources. Thanks to Heinz-Dieter Conradi for the pull request! - - - A NullPointerException when validating some QuestionnaireResponse reousrces - was fixed in the validator. Thanks to Heinz-Dieter Conradi for the pull request! - - - QuestionnaireResponse answers of type "text" may now be validated by the - FhirInstanceValidator. Thanks to Heinz-Dieter Conradi for the pull request! - - - The REST server has been modified so that the - Location]]> - header is no longer returned by the server on read or update responses. - This header was returned in the past, but this header is actually - inappropriate for any response that is not a create operation. - The - Content-Location]]> - will still be returned, and will hold the same contents. - - - The Postgres sample JPA project was fixed to use the current version - of HAPI FHIR (it was previously stuck on 2.2). Thanks to - Kai Liu for the pull request! - -
        - - - This release corrects an inefficiency in the JPA Server, but requires a schema - change in order to update. Prior to this version of HAPI FHIR, a CLOB column - containing the complete resource body was stored in two - tables: HFJ_RESOURCE and HFJ_RES_VER. Because the same content was stored in two - places, the database consumed more space than is needed to. -
        ]]> - In order to reduce this duplication, the - RES_TEXT and RES_ENCODING]]> - columns have been - dropped]]> - from the - HFJ_RESOURCE]]> - table, and the - RES_TEXT and RES_ENCODING]]> - columns have been - made NULLABLE]]> - on the - HFJ_RES_VER]]> - table. -
        ]]> - The following migration script may be used to apply these changes to - your database. Naturally you should back your database up prior to - making this change. - - ALTER TABLE hfj_resource DROP COLUMN res_text;
        - ALTER TABLE hfj_resource DROP COLUMN res_encoding;
        - ALTER TABLE hfj_res_ver ALTER COLUMN res_encoding DROP NOT NULL;
        - ALTER TABLE hfj_res_ver ALTER COLUMN res_text DROP NOT NULL;
        - ]]> -
        - - The validation module has been refactored to use the R4 (currently maintained) - validator even for DSTU3 validation. This is done by using an automatic - converter which converts StructureDefinition/ValueSet/CodeSystem resources - which are used as inputs to the validator. This change should fix a number - of known issues with the validator, as they have been fixed in R4 but - not in DSTU3. This also makes our validator much more maintainable - since it is now one codebase. - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Hibernate (JPA): 5.2.10.Final -> 5.2.12.Final
      • -
      • Spring (JPA): 5.0.0 -> 5.0.3
      • -
      • Thymeleaf (Web Tespage Overlay): 3.0.7.RELEASE -> 3.0.9.RELEASE
      • - - ]]> -
        - - A number of HAPI FHIR modules have been converted so that they now work - as OSGi modules. Unlike the previous OSGi module, which was a mega-JAR - with all of HAPI FHIR in it, this is simply the appropriate - OSGi manifest inside the existing JARs. Thanks to John Poth - for the Pull Request! -
        - Note that this does not cover all modules in the project. Current support includes: -
          -
        • HAPI-FHIR structures DSTU2, HL7ORGDSTU2, DSTU2.1, DSTU3, R4
        • -
        • HAPI-FHIR Resource validation DSTU2, HL7ORGDSTU2, DSTU2.1, DSTU3, R4
        • -
        • Apache Karaf features for all the above
        • -
        • Integration Tests
        • -
        - Remaining work includes: -
          -
        • HAPI-FHIR Server support
        • -
        • HAPI-FHIR narrative support. This might be tricky as Thymeleaf doesn't support OSGi.
        • -
        - ]]> -
        - - Fix a crash in the JSON parser when parsing extensions on repeatable - elements (e.g. Patient.address.line) where there is an extension on the - first repetition but not on subsequent repetitions of the - repeatable primitive. Thanks to Igor Sirkovich for providing a - test case! - - - Fix an issue where the JPA server crashed while attempting to normalize string values - containing Korean text. Thanks to GitHub user @JoonggeonLee for reporting! - - - An issue was solved where it was possible for server interceptors - to have both processingCompletedNormally and handleException called - if the stream.close() method threw an exception. Thanks to Carlos - Eduardo Lara Augusto for investigating! - - - The @TagListParam]]> annotation has been removed. This - annotation had no use after DSTU1 but never got deleted and was misleading. Thanks - to Angelo Kastroulis for reporting! - - - A new method overload has been added to IServerInterceptor: - outgoingResponse(RequestDetails, ResponseDetails, HttpServletRequest, HttpServletResponse) - ]]>. This new method allows an interceptor to completely replace - the resource being returned with a different resource instance, or - to modify the HTTP Status Code being returned. All other "outgoingResponse" - methods have been deprecated and are recommended to be migrated - to the new method. This new method (with its RequestDetails and ResponseDetails - parameters) should be flexible enough to - accommodate future needs which means that this should be the last - time we have to change it. - - - The HAPI-FHIR-CLI now explicitly includes JAXB dependencies in its combined JAR - file. These were not neccesary prior to Java 9, but the JDK (mercifully) does - not include JAXB in the default classpath as of Java 9. This means that - it is possible to perform Schematron validation on Java 9. Thanks to - John Grimes for reporting and suggesting a fix! - - - An experimental interceptor called VersionedApiConverterInterceptor has been added, - which automaticaly converts response payloads to a client-specified version - according to transforms built into FHIR. - - - Searches which were embedded in a Bundle as a transaction or batch operation did - not respect any chained method parameters (e.g. MedicationRequest?medication.code=123). - Thanks to @manjusampath for reporting! - - - A few fixes went into the build which should now allow HAPI FHIR - to build correctly on JDK 9.0. Currently building is supported on - JDK 8.x and 9.x only. - - - Client requests with an - Accept]]> - header value of - application/json]]> - will now be served with the non-legacy content type of - application/fhir+json]]> - instead of the legacy - application/json+fhir]]>. - Thanks to John Grimes for reporting! - - - Fixed a regression in server where a count parameter in the form - @Count IntegerType theCount]]> - caused an exception if the client made a request with - no count parameter included. Thanks to Viviana Sanz for reporting! - - - A bug in the JPA server was fixed where a Subscription incorrectly created - without a status or with invalid criteria would cause a crash during - startup. - - - ResponseHighlightingInterceptor now properly parses _format - parameters that include additional content (e.g. - _format=html/json;fhirVersion=1.0]]>) - - - Stale search deleting routine on JPA server has been adjusted - to delete one search per transaction instead of batching 1000 - searches per transaction. This should make the deletion logic - more tolerant of deleting very large search result sets. - - - Avoid refreshing the search parameter cache from an incoming client - request thread, which caused unneccesary delays for clients. - - - An occasional crash in the JPA was fixed when using unique search - parameters and updating a resource to no longer match - one of these search parameters. - - - Avoid an endless loop of reindexing in JPA if a SearchParameter is - created which indexed the SearchParameter resource itself - - - JPA server now performs temporary/placeholder ID substitution processing on elements in - resources which are of type "URI" in addition to the current substitution for - elements of type "Reference". Thanks to GitHub user @t4deon for supplying - a testcase! - - - Deleting a resource from the testpage overlay resulted in an error page after - clicking "delete", even though the delete succeeded. - - - A number of info level log lines have been reduced to debug level in the JPA server, in - order to reduce contention during heavy loads and reduce the amount of noise - in log files overall. A typical server should now see far less logging coming - from HAPI, at least at the INFO level. - - - JPA server now correctly indexes custom search parameters which - have multiple base resource types. Previously, the indexing could - cause resources of the wrong type to be returned in a search - if a parameter being used also matched that type. Thanks - to Dave Carlson for reporting! - - - A new IResourceProvider implementation called - HashMapResourceProvider - ]]> - has been added. This is a complete resource provider - implementation that uses a HashMap as a backing store. This class - is probably of limited use in real production systems, but it - cam be useful for tests or for static servers with small amounts - of data. - - - An issue in the JPA server was corrected where searching using - URI search parameters would sometimes not include the resource type in the - criteria. This meant, for example, that a search for - ValueSet?url=http://foo]]> would also - match any CodeSystem resource that happened to also have - that URL as the value for the "url" search parameter. Thanks - to Josh Mandel for reporting and supplying a test case! - - - DateParam class now has equals() and hashCode() implementations. Thanks - to Gaetano Gallo for the pull request! - - - Fix a bug where under certain circumstances, duplicate contained resources - could be output by the parser's encode methods. Thanks to - Frank Tao for supplying a test case! - - - The client LoggingInterceptor now includes the number of - milliseconds spent performing each call that is logged. - - - ReferenceParam has been enhanced to properly return the resource type to - user code in a server via the ReferenceType#getResourceType() method - if the client has specified a reference parameter with - a resource type. Thanks to @CarthageKing for the pull request! - - - An entry has been added to ResourceMetadataKeyEnum which allows extensions - to be placed in the resource metadata section in DSTU2 resource (this is - possible already in DSTU3+ resources as Meta is a normal model type, but - the older structures worked a bit differently. Thanks to GitHub user - sjanic for the contribution! - - - An example project has een contributed which shows how to use the CQL - framework in a server with HAPI FHIR JPA. Thanks to Chris Schuler - for the pull request! - - - A new module has been contributed called hapi-fhir-jpaserver-elasticsearch - which adds support for Elasticsearch instead of raw Lucene for fulltext - indexing. Testing help on this would be appreciated! Thanks to - Jiajing Liang for the pull request! - - - JAX-RS server now supports R4 and DSTU2_1 FHIR versions, which were - previously missing. Thanks to Clayton Bodendein for the pull - request! - - - AuthorizationInterceptor did not correctly handle authorization against - against a compartment where the compartment owner was specified - as a list of IDs. Thanks to Jiajing Liang for the pull request! - - - REST HOOK subscriptions in the JPA server now support having - an empty/missing Subscription.channel.payload value, which - is supported according to the FHIR specification. Thanks - to Jeff Chung for the pull request! - - - JPA Server Operation Interceptor create/update methods will now no - longer be fired if the create/update operation being performed - is a no-op (e.g. a conditional create that did not need to perform - any action, or an update where the contents didn't actually change) - - - JPA server sometimes updated resources even though the client - supplied an update with no actual changes in it, due to - changes in the metadata section being considered content - changes. Thanks to Kyle Meadows for the pull request! - - - A new example project has been added called hapi-fhir-jpaserver-dynamic, - which uses application/environment properties to configure which version - of FHIR the server supports and other configuration. Thanks to - Anoush Mouradian for the pull request! - - - A new example project showing the use of JAX-RS Server Side Events has - been added. Thanks to Jens Kristian Villadsen for the pull request! - - - An unneccesary reference to the Javassist library has been - removed from the build. Thanks to Łukasz Dywicki for the - pull request! - - - Support has been added to the JPA server for the :not modifier. Thanks - to Łukasz Dywicki for the pull request! - - - Suport for the :contains string search parameter modifier has been added to - the JPA server. Thanks to Anthony Sute for the pull request! - - - All instances of DefaultProfileValidationSupport (i.e. one for - each version of FHIR) have been fixed so that they explicitly - close any InputStreams they open in order to read the built-in - profile resources. Leaving these open caused resource starvation - in some cases under heavy load. - -
        - - - Support for custom search parameters has been backported in the JPA server - from DSTU3 back to DSTU2. As of this release of HAPI, full support for custom - search parameters exists in all supported versions of FHIR. - - - A new set of methods have been added to - IServerOperationInterceptor]]> - called - resourcePreCreate]]>, - resourcePreUpdate]]>, and - resourcePreDelete]]>. These - methods are called within the database transaction - (just as the existing methods were) but are invoked - prior to the contents being saved to the database. This - can be useful in order to allow interceptors to - change payload contents being saved. - - - A few redundant and no longer useful methods have been marked as - deprecated in - IServerInterceptor]]>. If you have implemented - custom interceptors you are recommended to migrate to the recommended - methods. - - - A new method has been added to RequestDetails called - setRequestContents()]]> which can be used - by interceptors to modify the request body before it - is parsed by the server. - - - Fix a crash in JPA server when performing a recursive - _include]]> which doesn't actually find any matches. - - - When encoding URL parameter values, HAPI FHIR would incorrectly escape - a space (" ") as a plus ("+") insetad of as "%20" as required by - RFC 3986. This affects client calls, as well as URLs generated by - the server (e.g. REST HOOK calls). Thanks to James Daily for reporting! - - - Searching in JPA server using a combination of _content and _id parameters - failed. Thanks to Jeff Weyer for reporting! - - - A new configuration option has been added to DaoConfig which allows newly created - resources to be assigned a UUID by the server instead of a sequential ID - - - An unneccesary column called "MYHASHCODE" was added to the - HFJ_TAG_DEF table in the JPA server schema - - - A few log entries emitted by the JPA server suring every search have been reduced - from INFO to DEBUG in order to reduce log noise - - - Fix an issue in JPA server where updating a resource sometimes caused date search indexes to - be incorrectly deleted. Thanks to Kyle Meadows for the pull request! - - - Servers did not return an ETag if the version was provided on a - DSTU3/R4 structure in the getMeta() version field instead of in the - getIdElement() ID. Thanks to GitHub user @Chrisjobling for reporting! - - - A bug was fixed in the JPA server when performing a validate operation with a mode - of DELETE on a server with referential integrity disabled, the validate operation would delete - resource reference indexes as though the delete was actually happening, which negatively - affected searching for the resource being validated. - - - The HAPI FHIR Server framework now has initial support for - multitenancy. At this time the support is limited to the server - framework (not the client, JPA, or JAX-RS frameworks). See - Server Documentation - ]]> - for more information. - - - - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Spring (JPA): 4.3.10 -> 5.0.0
      • -
      • Jackson (JPA): 2.8.1 -> 2.9.2
      • - - ]]> -
        - - The Android client module has been restored to working order, and no longer - requires a special classifier or an XML parser to be present in order to - work. This means that the hapi-fhir-android library is much less likely - to cause conflicts with other libraries imported into an Android application - via Gradle. -
        ]]> - See the - HAPI FHIR Android Documentation]]> - for more information. As a part of this fix, all dependencies on - the StAX API have been removed in environments where StAX is not - present (such as Android). The client will now detect this case, and - explicitly request JSON payloads from servers, meaning that Android clients - no longer need to include two parser stacks -
        - - A performance to the JPA server has been made which reduces the number - of writes to index tables when updating a resource with contents that - only make minor changes to the resource content. In many cases this can - noticeably improve update performance. - - - In FHIR DSTU3 the - ValueSet/$expand?identifier=foo]]> - and - ValueSet/$validate-code?identifier=foo]]> - parameters were changed to - ValueSet/$expand?url=foo]]> - and - ValueSet/$validate-code?url=foo]]> - respectively, but the JPA server had not caught up. The - JPA DSTU3 server has been adjusted to accept either "identifier" - or "url" (with "url" taking precedence), and the JPA R4 server - has been changed to only accept "url". - Thanks to Avinash Shanbhag for reporting! - - - Fix an error in JPA server when using Derby Database, where search queries with - a search URL longer than 255 characters caused a mysterious failure. Thanks to - Chris Schuler and Bryn Rhodes for all of their help in reproducing this issue. - - - JPA server now supports the use of the - Cache-Control]]> - header in order to allow the client to selectively disable the - search result cache. This directive can also be used to disable result paging - and return results faster when only a small number of results is needed. - See the - JPA Page]]> - for more information. - - - In certain cases in the JPA server, if multiple threads all attempted to - update the same resource simultaneously, the optimistic lock failure caused - a "gap" in the history numbers to occur. This would then cause a mysterious - failure when trying to update this resource further. This has been - resolved. - - - JPA Server search/history results now set the ID of the returned Bundle to - the ID of the search, meaning that if a search returns results from the Query - cache, it will reuse the ID of the previously returned Bundle - - - Fix a NullPointerException when validating a Bundle (in DSTU3/R4) with no - Bundle.type]]> value - - - The JPA server transaction operation (DSTU3/R4) did not correctly process the - If-Match header when passed in via - Bundle.entry.request.ifMatch]]> value - - - In Apache client, remove a log message at WARN level when the response does not - specify a charset. This log line often showed up any time a server was not supplying - a response, making client logs quite noisy - - - A new configuration item has been added to the JPA server DaoConfig - called - getCountSearchResultsUpTo()]]>. - This setting governs how many search results the search - coordinator should try to find before returning an initial - search response to the user, which has an effect on whether - the - Bundle.total]]> - field is always populated in search responses. This has now - been set to 20000 on out public server (fhirtest.uhn.ca) - so most search results should now include a total. - - - Remove a bunch of exceptions in the org.hl7.fhir.exception package from the - hapi-fhir-base module, as they were also duplicated in the - hapi-fhir-utilities module. - - - The DSTU2 XhtmlDt type has been modified so that it no longer uses - the StAX XMLEvent type as its internal model, and instead simply uses - a String. New methods called "parse" and "encode" have been added - to HAPI FHIR's XmlUtil class, which can be used to convert - between a String and an XML representation. This should allow - HAPI FHIR to run in environments where StAX is not available, such - as Android phones. - - - Restored the - org.hl7.fhir.r4.model.codesystem.*]]> - classes (which are Java Enums for the various FHIR codesystems). - These were accidentally removed in HAPI FHIR 3.0.0. Thanks to - GitHub user @CarthageKing for reporting! - - - The resource Profile Validator has been enhanced to not try to validate - bound fields where the binding strength is "example", and a crash was - resolved when validating QuestionnaireResponse answers with a type - of "choice" where the choice was bound to a ValueSet. - - - Remove the fake "Test" resource from DSTU2 structures. This was not - a real resource type, and caused conflicts with the .NET client. Thanks to - Vlad Ignatov for reporting! - - - Parsing a DSTU3/R4 custom structure which contained a field of - a custom type caused a crash during parsing. Thanks to - GitHub user @mosaic-hgw for reporting! - - - Client logic for checking the version of the connected - server to ensure it is for the correct version of FHIR now - includes a check for R4 servers. Thanks to Clayton Bodendein - for the pull request, including a number of great tests! - - - JAX-RS client framework now supports the ability to - register your own JAX-RS Component Classes against the client, - as well as better documentation about thread safety. Thanks - to Sébastien Rivière for the pull request! - - - Processing of the If-Modified-Since header on FHIR read operations was reversed, - returning a 304 when the resource had been modified recently. Thanks to - Michael Lawley for the pull request! - - - Add Prefer and Cache-Control]]> to the list of headers which are declared - as - being acceptable for CORS requests in CorsInterceptor, CLI, and JPA Example. - Thanks to Patrick Werner for the pull request! - - - DSTU2-hl7org and DSTU2.1 structures did not copy resource IDs when invoking - copyValues(). Thanks to Clayton Bodendein for the pull request! - - - When encoding a Binary resource, the Binary.securityContext field - was not encoded correctly. Thanks to Malcolm McRoberts for the pull - request with fix and test case! - - - Bundle resources did not have their version encoded when serializing - in FHIR resource (XML/JSON) format. - - - The Binary resource endpoint now supports the X-Security-Context]]> header when - reading or writing Binary contents using their native Content-Type (i.e exchanging - the raw binary with the server, as opposed to exchanging a FHIR resource). - - - When paging through multiple pages of search results, if the - client had requested a subset of resources to be returned using the - _elements]]> parameter, the elements list - was lost after the first page of results. - In addition, elements will not remove elements from - search/history Bundles (i.e. elements from the Bundle itself, as opposed - to elements in the entry resources) unless the Bundle elements are - explicitly listed, e.g. _include=Bundle.total]]>. - Thanks to @parisni for reporting! - - - Add support for Spring Boot for initializing a number of parts of the library, - as well as several examples. - See the - Spring Boot samples]]> - for examples of how this works. - Thanks to Mathieu Ouellet for the contribution! - - - JPA server now has lucene index support moved to separate classes from the entity - classes in order to facilitate support for ElasticSearch. Thanks to Jiang Liang - for the pull request! - hibernate.search.model_mapping. See this line in the example project. - ]]> - - - A new client interceptor has been added called - AdditionalRequestHeadersInterceptor, which allows - a developer to add additional custom headers to a - client requests. - Thanks to Clayton Bodendein for the pull request! - - - An issue was fixed in JPA server where extensions on primitives which - are nestedt several layers deep are lost when resources are retrieved - - - Conditional deletes in JPA server were incorrectly denied by AuthorizationInterceptor - if the delete was permitted via a compartment rule. Thanks to Alvin Leonard for the - pull request! - - - JAX-RS server module was not able to generate server CapabilityStatement for - some versions of FHIR (DSTU2_HL7ORG, DSTU2_1, or R4). Thanks to Clayton Bodendein for the Pull Request! - - - When a server method throws a DataFormatException, the error will now be converted into - an HTTP 400 instead of an HTTP 500 when returned to the client (and a stack - trace will now be returned to the client for JAX-RS server if configured to - do so). Thanks to Clayton Bodendein for the pull request! - - - JAX-RS server conformance provider in the example module passed in the - server description, server name, and server version in the incorrect order. - Thanks to Clayton Bodendein for the pull request! - - - The learn more links on the website home page had broken links. Thanks to - James Daily for the pull request to fix this! - - - Prevent a crash in AuthorizationInterceptor when processing transactions - if the interceptor has rules declared which allow resources to be read/written - by "any ID of a given type". Thanks to GitHub user @dconlan for the pull - request! - -
        - - - Support for FHIR R4 (current working draft) has been added]]> - (in a new module called hapi-fhir-structures-r4]]>) - and - support for FHIR DSTU1 (hapi-fhir-structures-dstu]]>) - has been removed]]>. Removing support for the legacy - DSTU1 FHIR version was a difficult decision, but it allows us the - opportunitity to clean up the codebase quite a bit, and remove some - confusing legacy parts of the API (such as the legacy Atom Bundle class). -
        ]]> - A new redesigned table of HAPI FHIR versions to FHIR version support has been - added to the Download Page]]> -
        - - HAPI FHIR's modules have been restructured for more consistency and less coupling - between unrelated parts of the API. -
        ]]> - A new complete list of HAPI FHIR modules has been added to the - Download Page]]>. Key changes - include: - -
      • - HAPI FHIR's client codebase has been moved out of hapi-fhir-base - and in to a new module called hapi-fhir-client. Client users now need - to explicitly add this JAR to their project (and non-client users now no longer - need to depend on it) -
      • -
      • - HAPI FHIR's server codebase has been moved out of hapi-fhir-base - and in to a new module called hapi-fhir-server. Server users now need - to explicitly add this JAR to their project (and non-server users now no longer - need to depend on it) -
      • -
      • - As a result of the client and server changes above, we no longer need to produce - a special Android JAR which contains the client, server (which added space but was - not used) and structures. There is now a normal module called hapi-fhir-android - which is added to your Android Gradle file along with whatever structures JARs you - wish to add. See the - Android Integration Test - to see a sample project using HAPI FHIR 3.0.0. Note that this has been reported to - work by some people but others are having issues with it! In order to avoid delaying - this release any further we are releasing now despite these issues. If you are an Android - guru and want to help iron things out please get in touch. If not, it might be a good - idea to stay on HAPI FHIR 2.5 until the next point release of the 3.x series. -
      • -
      • - A new JAR containing FHIR utilities called hapi-fhir-utilities has been - added. This JAR reflects the ongoing harmonization between HAPI FHIR and the FHIR - RI codebases and is generally required in order to use HAPI at this point (if you - are using a dependency manager such as Maven or Gradle it will be brought in to your - project automatically as a dependency) -
      • - - ]]> -
        - - In order to allow the reoganizations and decoupling above to happen, a number of important classes - and interfaces have been moved to new packages. A sample list of these changes is listed - below. When upgrading to 3.0.0 your project may well show a number of compile errors - related to missing classes. In most cases this can be resolved by simply removing the HAPI - imports from your classes and asking your IDE to "Organize Imports" once again. This is an - annoying change we do realize, but it is neccesary in order to allow the project to - continue to grow. - -
      • IGenericClient moved from package ca.uhn.fhir.rest.client to package ca.uhn.fhir.rest.client.api
      • -
      • IRestfulClient moved from package ca.uhn.fhir.rest.client to package ca.uhn.fhir.rest.client.api
      • -
      • AddProfileTagEnum moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.context.api
      • -
      • IVersionSpecificBundleFactory moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.context.api
      • -
      • BundleInclusionRule moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.context.api
      • -
      • RestSearchParameterTypeEnum moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.rest.api
      • -
      • EncodingEnum moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.rest.api
      • -
      • Constants moved from package ca.uhn.fhir.rest.server to package ca.uhn.fhir.rest.api
      • -
      • IClientInterceptor moved from package ca.uhn.fhir.rest.client to package ca.uhn.fhir.rest.client.api
      • -
      • ITestingUiClientFactory moved from package ca.uhn.fhir.util to package ca.uhn.fhir.rest.server.util
      • - - ]]> -
        - - Because the Atom-based DSTU1 Bundle class has been removed from the library, users of the - HAPI FHIR client must now always include a Bundle return type in search calls. For example, - the following call would have worked previously: - -Bundle bundle = client.search().forResource(Patient.class)
        - .where(new TokenClientParam("gender").exactly().code("unknown"))
        - .prettyPrint()
        - .execute();

        - ]]> - This now needs an explicit returnBundle statement, as follows: - -Bundle bundle = client.search().forResource(Patient.class)
        - .where(new TokenClientParam("gender").exactly().code("unknown"))
        - .prettyPrint()
        - .returnBundle(Bundle.class)
        - .execute();
        - ]]> -
        - - The version of a few dependencies have been bumped to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Gson (JSON Parser): 2.8.0 -> 2.8.1
      • -
      • Commons-lang3 (Everywhere): 3.5 -> 3.6
      • - -
      • Apache HttpClient (FHIR Client): 4.5.2 -> 4.5.3
      • -
      • Apache HttpCore (FHIR Client): 4.4.5 -> 4.4.6
      • -
      • Phloc Commons (Schematron Validator): 4.4.6 -> 4.4.11
      • -
      • Hibernate (JPA): 5.2.9 -> 5.2.10
      • -
      • Hibernate Search (JPA): 5.7.0 -> 5.7.1
      • -
      • Spring (JPA): 4.3.7 -> 4.3.10
      • -
      • Spring Data JPA (JPA): 1.10.4 -> 1.11.6
      • -
      • Guava (JPA): 22.0 -> 23.0
      • -
      • Thymeleaf (Testpage Overlay): 3.0.2 -> 3.0.7
      • -
      • OkHttp (Android): 3.4.1 -> 3.8.1
      • - - ]]> -
        - - JPA Subscription support has been refactored. A design contributed - by Jeff Chung for the REST Hook subscription module has been ported - so that Websocket subscriptions use it too. This design uses an - interceptor to scan resources as they are processed to test whether - they should be delivered to subscriptions, instead of using a - polling design. -
        ]]> - In addition, this scanning has been reworked to happen in a separate - thread from the main storage thread, which should improve - performance and scalability of systems with multiple - subscriptions. Thanks to Jeff for all of his work on this! -
        - - hapi-fhir-client-okhttp project POM had dependencies on both - hapi-fhir-structures-dstu2 and hapi-fhir-structures-dstu3, which - meant that any project using ookhttp would import both structures - JARs. This has been removed. - - - JPA server is now able to handle placeholder IDs (e.g. urn:uuid:00....000) - being used in Bundle.entry.request.url as a part of the conditional URL - within transactions. - - - Schematron validator now applies invariants to resources within a Bundle, not - just to the outer Bundle resource itself - - - Server and Client both still included Category header for resource tags even though - this feature was only present in FHIR DSTU1 and was removed from the specification in - FHIR DSTU2. The presence of these headers sometimes caused parsed resource instances - to contain duplicate tags - - - When using the AuthorizationInterceptor with the JPA server, when a client is updating a resource - from A to B, the user now needs to have write permission for both A and B. This is particularly - important for cases where (for example) an Observation is being updated from having a subject of - Patient/A to Patient/B. If the user has write permission for Patient/B's compartment, this would - previously have been allowed even if the user did not have access to write to Patient/A's compartment. - Thanks to Eeva Turkka for reporting! - - - IServerOperationInterceptor now has a new method - resourceUpdated(RequestDetails, IBaseResource, IBaseResource)]]> - which replaces the previous - resourceUpdated(RequestDetails, IBaseResource)]]>. This allows - interceptors to be notified of resource updates, but also see what the resource - looked like before the update. This change was made to support the change above, but - seems like a useful feature all around. - - - Allow DateParam (used in servers) to handle values with MINUTE precision. Thanks to - Christian Ohr for the pull request! - - - Fix HTTP 500 error in JPA server if a numeric search parameter was supplied with no value, e.g. - GET /Observation?value-quantity=]]> - - - JPA server transaction processing now honours the Prefer header and includes - created and updated resource bodies in the response bundle if it is set - appropriately. - - - Optimize queries in JPA server remove a few redundant select columns when performing - searches. This provides a slight speed increase in some cases. - - - Add configuration to JPA server DaoConfig that allows a maximum - number of search results to be specified. Queries will never return - more than this number, which can be good for avoiding accidental - performance problems in situations where large queries should not be - needed - - - Prevent duplicates in $everything query response in JPA server. Thanks to @vlad-ignatov - for reporting! - - - Fix issue in calling JPA server transactions programmatically where resources - are linked by object reference and not by ID where indexes were not correctly - generated. This should not affect most users. - - - Fix issue in SubscriptionInterceptor that caused interceptor to only - actually notify listeners of the first 10 subscriptions. Thanks to Jeff Chung - for the pull request! - - - Fix potential ConcurrentModificationException when adding subscriptions while - running under heavy load. Thanks to Jeff Chung for the pull request! - - - JPA search now uses hibernate ScrollableResults instead of plain JPA List. This - should improve performance over large search results. - - - JPA servers with no paging provider configured, or with a paging provider other than - DatabaseBackedPagingProvider will load all results in a single pass and keep them - in memory. Using this setup is not a good idea unless you know for sure that you - will never have very large queries since it means that all results will be loaded into - memory, but there are valid reasons to need this and it will perform better than - paging to the database in that case. This fix also resolves a NullPointerException - when performing an $everything search. Thanks to Kamal Othman for reporting! - - - Correct an issue in JPA server on Postgres where searches with a long search URL - were not able to be automatically purged from the database after they were scheduled - for deletion. Thanks to Ravi Kuchi for reporting! - - - Add an optional and configurable hard limit on the total number of meta items - (tags, profiles, and security labels) on an individual resource. The default - is 1000. - - - When executing a search (HTTP GET) as a nested operation in in a transaction or - batch operation, the search now returns a normal page of results with a link to - the next page, like any other search would. Previously the search would return - a small number of results with no paging performed, so this change brings transaction - and batch processing in line with other types of search. - - - JPA server no longer returns an OperationOutcome resource as the first resource - in the Bundle for a response to a batch operation. This behaviour was previously - present, but was not specified in the FHIR specification so it caused confusion and - was inconsistent with behaviour in other servers. - - - Fix a regression in HAPI FHIR 2.5 JPA server where executing a search in a - transaction or batch operation caused an exception. Thanks to Ravi Kuchi for - reporting! - - - Correct an issue when processing transactions in JPA server where updates and - creates to resources with tags caused the tags to be created twice in the - database. These duplicates were utomatically filtered upon read so this issue - was not user-visible, but it coule occasionally lead to performance issues - if a resource containing multiple tags was updated many times via - transactions. - - - JPA server should not allow creation of resources that have a reference to - a resource ID that previously existed but is now deleted. Thanks to Artem - Sopin for reporting! - - - JpaConformanceProvider now has a configuration setting to enable and - disable adding resource counts to the server metadata. - - - Avoid a deadlock in JPA server when the RequestValidatingInterceptor is being - used and a large number of resources are being created by clients at - the same time. - - - Testpage Overlay's transaction method did not work if the response - Bundle contained any entries that did not contain a resource (which - is often the case in more recent versions of HAPI). Thanks to Sujay R - for reporting! - - - When the server was returning a multi-page search result where the - client did not explicitly request an encoding via the _format - parameter, a _format parameter was incorrectly added to the paging - links in the response Bundle. This would often explicitly request - XML encoding because of the browser Accept header even though - this was not what the client wanted. - - - Enhancement to ResponseHighlighterInterceptor where links in the resource - body are now converted to actual clickable hyperlinks. Thanks to Eugene Lubarsky - for the pull request! - - - BanUnsupportedHttpMethodsInterceptor has been modified so that it now allows - HTTP PATCH to proceed. - - - Enhancement to ResponseHighlighterInterceptor so that it now can be configured - to display the request headers and response headers, and individual lines - may be highlighted. - - - AuthorizationInterceptor did not permit PATCH operations to proceed even - if the user had write access for the resource being patched. - - - Fix an issue in HapiWorkerContext where structure definitions are - not able to be retrieved if they are referred to by their - relative or logical ID. This affects profile tooling such as - StructureMapUtilities. Thanks to Travis Lukach for reporting and - providing a test case! - - - Add link to DSTU3 JavaDocs from documentation index. Thanks - to Vadim Peretokin for the pull request! - - - Fix a typo in the documentation. Thanks to Saren Currie - for the pull request! - - - Add a command line flag to the CLI tool to allow configuration of the - server search result cache timeout period. Thanks to Eugene Lubarsky - for the pull request! - - - Correct an issue with the model classes for STU3 where any classes - containing the @ChildOrder annotation (basically the conformance - resources) will not correctly set the order if any of the - elements are a choice type (i.e. named "foo[x]"). Thanks to - GitHub user @CarthageKing for the pull request! - - - Fix potential deadlock in stale search deleting task in JPA server, as well - as potential deadlock when executing transactions containing nested - searches when operating under extremely heavy load. - - - JPA server transaction operations now put OperationOutcome resources resulting - from actions in - Bundle.entry.response.outcome]]> - instead of the previous - Bundle.entry.resource]]> - - - An issue was corrected where search parameters containing negative numbers - were sometimes treated as positive numbers when processing the search. Thanks - to Keith Boone for reporting and suggesting a fix! - - - Fix an unfortunate typo in the custom structures documentation. Thanks to - Jason Owen for the PR! - - - Correct an issue in the validator (DSTU3/R4) where elements were not always - correctly validated if the element contained only a profiled extension. Thanks - to Sébastien Rivière for the pull request! - - - Testing UI now has a dropdown for modifiers on token search. Thanks - to GitHub user @dconlan for the pull request! - - - When parsing an incomplete ID with the form http://my.org/Foo]]> into - IdDt and IdType objects, the Foo portion will now be treated as the resource type. - Previously my.org was treated as the resource type and Foo was treated as the ID. Thanks - to GitHub user @CarthageKing for the pull request! - - - Extensions on ID datatypes were not parsed or serialized correctly. Thanks to - Stephen Rivière for the pull request! - - - Fix a bug in REST Hook Subscription interceptors which prevented subscriptions - from being activated. Thanks to Jeff Chung for the pull request! - - - Fix broken links in usage pattern diagram on website. Thanks to - Pascal Brandt for the pull request! - - - Fix incorrect FHIR Version Strings that were being outputted and verified in the - client for some versions of FHIR. Thanks to Clayton Bodendein for the - pull request! - - - Add a new constructor to SimpleRequestHeaderInterceptor which allows a complete header - to be passed in (including name and value in one string) - - - REST Hook subscriptions now honour the Subscription.channel.header field - - - DSTU2 validator has been enhanced to do a better job handling - ValueSets with expansions pointing to other ValueSets - - - REST HOOK subscriptions now use HTTP PUT if there is a payload type - specified, regardless of whether the source event was a create or an - update - - - Add appropriate import statements for logging to JPA demo code. Thanks to - Rob Hausam for the pull request! - - - Add some browser performance logging to ResponseHighlightingInterceptor. Thanks - to Eugene Lubarsky for the pull request, and for convincing James not to - optimize something that did not need optimizing! - - - A new config property has been added to the JPA seerver DaoConfig called - "setAutoCreatePlaceholderReferenceTargets". - This property causes references to unknown resources in created/updated resources to have a placeholder - target resource automatically created. - - - The server LoggingInterceptor has had a variable called - processingTimeMillis]]> which logs the number - of milliseconds the server took to process a given request since - HAPI FHIR 2.5, but this was not documented. This variable has now been - documented as a part of the available features. - - - A new experimental feature has been added to the JPA server which allows - you to define certain search parameter combinations as being resource keys, - so that a database constraint will prevent more than one resource from - having a matching pair - - - When using the client LoggingInterceptor in non-verbose mode, the - log line showing the server's response HTTP status will now also include - the returned - Location]]> header value as well - - - A new flag has been add to the CLI upload-definitions command - "-e" which allows skipping particular resources - - - An issue in JPA server has been corrected where if a CodeSystem - resource was deleted, it was not possible to create a new resource - with the same URI as the previous one - - - When uploading a Bundle resource to the server (as a collection or - document, not as a transaction) the ID was incorrectly stripped from - resources being saved within the Bundle. This has been corrected. - - - Subscriptions in JPA server now support "email" delivery type through the - use of a new interceptor which handles that type - - - JPA server can now be configured to not support - :missing]]> modifiers, which - increases write performance since fewer indexes are written - - - A new JPA configuration option has been added to the DaoConfig which allows - support for the :missing]]> search parameter modifier - to be enabled or disabled, and sets the default to DISABLED. -
        ]]> - Support for this parameter causes many more index rows to be inserted in the database, - which has a significant impact on write performance. A future HAPI update may allow these - rows to be written asynchronously in order to improve this. -
        -
        - - -
        -
          -
        • - Searches with multiple search parameters of different - datatypes (e.g. find patients by name and date of birth) - were previously joined in Java code, now the join is - performed by the database which is faster -
        • -
        • - Searches which returned lots of results previously has all - results streamed into memory before anything was returned to - the client. This is particularly slow if you do a search for - (say) "get me all patients" since potentially thousands or - even millions of patients' IDs were loaded into memory - before anything gets returned to the client. HAPI FHIR - now has a multithreaded search coordinator which returns - results to the client as soon as they are available -
        • -
        • - Search results will be cached and reused (so that if a client - does two searches for "get me all patients matching FOO" - with the same FOO in short succession, we won't query the DB - again but will instead reuse the cached results). Note that - this can improve performance, but does mean that searches can - return slightly out of date results. Essentially what this means - is that the latest version of individual resources will always - be returned despite this cacheing, but newly created resources - that should match may not be returned until the cache - expires. By default this cache has been set to one minute, - which should be acceptable for most real-world usage, but - this can be changed or disabled entirely. -
        • -
        • - Updates which do not actually change the contents of the resource - can optionally be prevented from creating a new version - of the resource in the database -
        • -
        -

        - Existing users should delete the - HFJ_SEARCH, - HFJ_SEARCH_INCLUDE, - and - HFJ_SEARCH_RESULT - tables from your database before upgrading, as the structure of these tables - has changed and old search results can not be reused. - ]]> -
        - - AuthorizationInterceptor did not correctly handle paging requests - (e.g. requests for the second page of results for a search operation). - Thanks to Eeva Turkka for reporting! - - - Add configuration property to DSTU3 FhirInstanceValidator to - allow client code to change unknown extension handling behaviour. - - - Fix concurrency issues in FhirContext that were causing issues when - starting a context up on Android. Thanks to GitHub issue @Jaypeg85 for - the pull request! - - - Fix an issue in the JPA server if a resource has been previously - saved containing vocabulary that is no longer valid. This only really - happened if you were using a non-final version of FHIR (e.g. using DSTU3 - before it was finalized) but if you were in this situation, upgrading HAPI - could cause you to have old codes that no longer exist in your database. This - fix prevents these from blocking you from accesing those resources. - - - CLI now defaults to DSTU3 mode if no FHIR version is specified - - - Server and annotation-client @History annotation now allows DSTU3+ resource - types in the type= property - - - JSON Parser gave a very unhelpful error message (Unknown attribute 'value' found during parse) - when a scalar value was found in a spot where an object is expected. This has been corrected to - include much more information. Thanks to GitHub user @jasminas for reporting! - - - DaoConfig#setInterceptors() has been un-deprecated. It was previously deprecated as - we thought it was not useful, but uses have been identified so it turns out this method - will live after all. Interceptors registered to this method will now be treated - appropriately if they implement IServerOperationInterceptor too. - - - JPA server did not correctly support searching on a custom search parameter whose - path pointed to an extension, where the client used a chained value. - - - Fix issue where the JSON parser sometimes did not encode DSTU3 extensions on the root of a - resource which have a value of type reference. - - - Server now respects the If-Modified-Since header and will return an HTTP 304 if appropriate - for read operations. - - - JPA server did not correctly process :missing qualifier on date parameters - - - AppacheHttpClient did not always respect the charset in the response - Content-Type header. Thanks to Gijsbert van den Brink for the pull request! - - - Fix XhtmlParser to correctly handle hexadecimal escaped literals. Thanks to - Gijsbert van den Brink for the Pull Request! - - - JPA server now has configurable properties that allow referential integrity - to be disabled for both writes and deletes. This is useful in some cases - where data integrity is not wanted or not possible. It can also be useful - if you want to delete large amounts of interconnected data quickly. -
        ]]> - A corresponding flag has been added to the CLI tool as well. -
        - - JPA server did not correctly support searching on a custom search parameter whose - path pointed to an extension, where the client used a chained value. - - - Fix dependency on commons-codec 1.4 in hapi-fhir-structures-dstu3, which was - preventing this library from being used on Android because Android includes - an older version of commons-codec. - - - JPA server failed to index search parameters on paths containing a decimal - data type - - - Validator incorrectly rejected references where only an identifier was populated - - - Make error handler in the client more tolerant of errors where no response has - been received by the client when the error happens. Thanks to GitHub - user maclema for the pull request! - - - Add a check in JPA server that prevents completely blank tags, profiles, and security labels - from being saved to the database. These were filtered out anyhow when the - result was returned back to the client but they were persisted which - just wasted space. - - - Loading the build-in profile structures (StructureDefinition, ValueSet, etc) is now done in - a synchronized block in order to prevent multiple loads happening if the server processes - multiple validations in parallel threads right after startup. Previously a heavy load could - cause the server to run out of memory and lock up. Thanks to Karl M Davis - for analysis and help fixing this! - - - Fix bad ValueSet URL in DeviceRequest profile definition for STU3 which - was preventing the CLI from uploading definitions correctly. Thanks to - Joel Schneider for the Pull Request! - - - Improve handling in JPA server when doing code:above and code:below - searches to use a disjunction of AND and IN in order to avoid failures - under certain conditions. Thanks to Michael Lawley for the pul request! - - - Fix an error where the JPA server sometimes failed occasional requests - with a weird NullPointerException when running under very large concurrent - loads. Thanks to Karl M. Davis for reporting, investigating, and ultimately - finding a solution! - -
        - - - This release brings the DSTU3 structures up to FHIR R3 (FHIR 3.0.1) definitions. Note that - there are very few changes between the DSTU3 structures in HAPI FHIR 2.3 and - the ones in HAPI FHIR 2.4 since the basis for the DSTU3 structures in HAPI FHIR - 2.3 was the R3 QA FHIR version (1.9.0) but this is the first release of - HAPI FHIR to support the final/complete R3 release. - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Hibernate (JPA): 5.2.7 -> 5.2.9
      • -
      • Hibernate Search (JPA): 5.5.7.CR1 -> 5.2.7.Final
      • -
      • Hibernate Validator (JPA): 5.3.4 -> 5.4.1
      • -
      • Spring (JPA): 4.3.6 -> 4.3.7
      • -
      • Gson (Core): 2.7 -> 2.8.0
      • -
      • Guava (JPA): 19.0 -> 21.0
      • -
      • SLF4j (Core): 1.7.21 -> 1.7.25
      • -
      • Logback (Core): 1.1.7 -> 1.2.2
      • - - ]]> -
        - - hapi-fhir-jpaserver-example now includes the - Prefer]]> header in the list of - CORS headers. Thanks to GitHub user @elnin0815 for - the pull request! - - - AuthorizationInterceptor can now allow make read or write - authorization decisions on a resource by instance ID - - - Remove SupportingDocumentation resource from DSTU2 structures. This isn't - actually a resource in FHIR DSTU2 and its inclusion causes errors on clients - that don't understand what it is. Thanks to Travis Cummings and Michele Mottini for pointing this out. - - - Web testing UI displayed an error when a transaction was pasted into the UI - for a DSTU2 server. Thanks to Suresh Kumar for reporting! - - - DaoConfig#setAllowInlineMatchUrlReferences() now defaults to - true]]> since inline conditional references - are now a part of the FHIR specification. Thanks to Jan Dědek for - pointing this out! - - - hapi-fhir-jpaserver-base now exposes a - FhirInstanceValidator bean named "myInstanceValidatorDstu2"]]> - for DSTU2. A similar bean for DSTU3 was previously implemented. - - - hapi-fhir-jpaserver-example project now defaults to STU3 mode instead of - the previous DSTU2. Thanks to Joel Schneider for the pull request! - - - JPA server now has a setting on the DaoConfig to force it to treat - certain reference URLs or reference URL patterns as logical URLs instead - of literal ones, meaning that the server will not try to resolve these - URLs. Thanks to Eeva Turkka for the suggestion! - - - Add a utility method to JPA server: - IFhirResourceDao#removeTag(IIdType, TagTypeEnum, String, String)]]>. This allows - client code to remove tags - from a resource without having a servlet request object in context. - - - JPA server was unable to process custom search parameters where - the path pointed to an extension containing a reference. Thanks - to Ravi Kuchi for reporting! - - - Servers in DSTU2.1 mode were incorrectly using the legacy mimetypes instead - of the new STU3 ones. Thanks to Michael Lawley for the pull request! - - - Add an option to ParserOptions that specifies that when parsing a bundle, the - ID found in the Bundle.entry.fullUrl should not override the ID found - in the Resource.id field. Technically these fields must always supply the - same ID in order for a server to be considered conformant, but this option allows - you to deal with servers which are behaving badly. Thanks to - GitHub user CarthageKing for the pul request! - - - Remove unneccesary whitespace in the text areas on the testing - web UI. Thanks to GitHub user @elnin0815 for the pull request! - - - In JAX-RS server it is now possible to change the server exception handler - at runtime without a server restart. - Thanks to Sebastien Riviere for the - pull request! - - - Fix a potential race condition when the FhirContext is being accessed by many threads - at the same time right as it is initializing. Thanks to Ben Spencer for the - pull request! - -
        - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Hibernate (JPA): 5.1.0 -> 5.2.7
      • -
      • Hibernate Search (JPA): 5.5.4 ->p; 5.7.0.CR1
      • -
      • Hibernate Validator (JPA): 5.2.4 ->p; 5.3.4
      • -
      • Spring (JPA): 4.3.1 -> 4.3.6
      • - - ]]> -
        - - The JPA server now supports custom search parameters in DSTU3 - mode. This allows users to create search parameters which contain - custom paths, or even override and disable existing search - parameters. - - - CLI example uploader couldn't find STU3 examples after CI server - was moved to build.fhir.org - - - Fix issue in JPA subscription module that prevented purging stale - subscriptions when many were present on Postgres - - - Server interceptor methods were being called twice unnecessarily - by the JPA server, and the DaoConfig interceptor registration - framework was not actually useful. Thanks to GitHub user - @mattiuusitalo for reporting! - - - AuthorizationInterceptor on JPA server did not correctly - apply rules on deleting resources in a specific compartment - because the resource metadata was stripped by the JPA server - before the interceptor could see it. Thanks to - Eeva Turkka for reporting! - - - JPA server exported CapabilityStatement includes - double entries for the _id parameter and uses the - wrong type (string instead of token). Thanks to - Robert Lichtenberger for reporting! - - - Custom resource types which extend Binary must not - have declared extensions since this is invalid in - FHIR (and HAPI would just ignore them anyhow). Thanks - to Thomas S Berg for reporting! - - - Standard HAPI zip/tar distributions did not include the project - sources and JavaDoc JARs. Thanks to Keith Boone for pointing - this out! - - - Server AuthorizationInterceptor always rejects history operation - at the type level even if rules should allow it. - - - JPA server terminology service was not correctly validating or expanding codes - in SNOMED CT or LOINC code systems. Thanks to David Hay for reporting! - - - Attempting to search for an invalid resource type (e.g. GET base/FooResource) should - return an HTTP 404 and not a 400, per the HTTP spec. Thanks to - GitHub user @CarthageKing for the pull request! - - - When parsing a Bundle containing placeholder fullUrls and references - (e.g. "urn:uuid:0000-0000") the resource reference targets did not get - populated with the given resources. Note that as a part of this - change, IdType and IdDt]]> have been modified - so that when parsing a placeholder ID, the complete placeholder including the - "urn:uuid:" or "urn:oid:" prefix will be placed into the ID part. Previously, - the prefix was treated as the base URL, which led to strange behaviour - like the placeholder being treated as a real IDs. Thanks to GitHub - user @jodue for reporting! - - - Declared extensions with multiple type() options listed in the @Child - annotation caused a crash on startup. Now this is supported. - - - STU3 XHTML parser for narrative choked if the narrative contained - an &rsquot;]]> entity string. - - - When parsing a quantity parameter on the server with a - value and units but no system (e.g. - GET [base]/Observation?value=5.4||mg]]>) - the unit was incorrectly treated as the system. Thanks to - @CarthageKing for the pull request! - - - Correct a typo in the JPA ValueSet ResourceProvider which prevented - successful operation under Spring 4.3. Thanks to - Robbert van Waveren for the pull request! - - - Deprecate the method - ICompositeElement#getAllPopulatedChildElementsOfType(Class)]]> - as it is no longer used by HAPI and is just an annoying step - in creating custom structures. Thanks to Allan Bro Hansen - for pointing this out. - - - CapturingInterceptor did not buffer the response meaning - that in many circumstances it did not actually capture - the response. Thanks to Jenny Syed of Cerner for - the pull request and contribution! - - - Clean up dependencies and remove Eclipse project files from git. Thanks to - @sekaijin for the pull request! - - - When performing a conditional create in a transaction in JPA server, - if a resource already existed matching the conditional expression, the - server did not change the version of the resource but did update the body - with the passed in body. Thanks to Artem Sopin for reporting and providing a test - case for this! - - - Client revincludes did not include the :recurse modifier. Thanks to - Jenny Meinsma for pointing this out on Zulip! - - - JPA server did not return an OperationOutcome in the response for - a normal delete operation. - - - Fix an issue in JPA server where _history results were kept in memory instead - of being spooled to the database as they should be. Note that as a part of this fix - a new method was added to - IBundleProvider called getUuid()]]>. This - method may return null]]> in any current cases. - - - Expanding a ValueSet in JPA server did not correctly apply - ?filter=]]> parameter when the ValueSet - being expanded had codes included explicitly (i.e. not by - is-a relationship). Thanks to David Hay for reporting! - - - JPA validator incorrectly returned an HTTP 400 instead of an HTTP 422 when - the resource ID was not present and required, or vice versa. Thanks to - Brian Postlethwaite for reporting! - - - When using an annotation based client, a ClassCastException would - occur under certain circumstances when the response contained - contained resources - - - JPA server interceptor methods for create/update/delete provided - the wrong version ID to the interceptors - - - A post-processing hook for subclasses of BaseValidatingInterceptor is now available. - - - AuthorizationInterceptor can now authorize (allow/deny) extended operations - on instances and types by wildcard (on any type, or on any instance) - - - When RequestValidatingInterceptor is used, the validation results - are now populated into the OperationOutcome produced by - create and update operations - - - Add support for the $process-message operation to fluent client. - Thanks to Hugo Soares for the pull request! - - - Parser can now be configured when encoding to use a specific - base URL for extensions. Thanks to Sebastien Riviere for the - pull request! - - - Correct the resource paths for the DSTU2.1 validation resources, - allowing the validator to correctly work against those structures. - Thanks to Michael Lawley for the pull request! - - - XML Parser failed to parse large field values (greater than 512 Kb) - on certain platforms where the StAX parser was overridden. Thanks to - GitHub user @Jodue for the pull request! - - - Remove an unneccesary database flush when saving large code systems to - the JPA database, improving performance of this operation. Thanks to - Joel Schneider for the pull request and analysis! - - - A new post-processing hook for subclasses of BaseValidatingInterceptor is now - available. The hook exposes the request details on validation failure prior to throwing an - UnprocessableEntityException. - -
        - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - - -
      • Derby (CLI): 10.12.1.1 -> 10.13.1.1
      • -
      • Jetty (CLI): 9.3.10.v20160621 -> 9.3.14.v20161028
      • -
      • JAnsi (CLI): 1.13 -> 1.14
      • -
      • Phloc Commons (SCH Validator): 4.4.5 -> 4.4.6
      • - - ]]> -
        - - Fix issue in AuthorizationIntetceptor where - transactions are blocked even when they - should not be - - - Fix regression in HAPI FHIR 2.1 JPA - server where some search parameters on - metadata resources did not appear - (e.g. "StructureDefinition.url"). Thanks - to David Hay for reporting! - - - Add ability to JPA server for disabling stale search - expiry. This is useful if you are deploying the server - to a cluster. - - - RestfulServer with no explicitly set FhirContext - fails to detect the presents of DSTU3 structures. Thanks - to GitHub user @vijayt27 for reporting! - - - As the - eBay CORS interceptor]]> - project - has gone dormant, we have introduced a new - HAPI server interceptor which can be used to implement CORS support - instead of using the previously recommended Servlet Filter. All server - examples as well as the CLI have been switched to use this new interceptor. - See the - CORS Documentation]]> - for more information. - - - Make the parser configurable so that when - parsing an invalid empty value (e.g. - {"status":""}]]>) the - parser will either throw a meaningful exception - or log a warning depending on the configured - error handler. - - - Fix issue when serializing resources that have - contained resources which are referred to - from multiple places. Sometimes when serializing - these resources the contained resource section - would contain duplicates. Thanks to Hugo Soares - and Stefan Evinance for reporting and providing - a test case! - - - Allow client to gracefully handle running in DSTU3 mode - but with a structures JAR that does not contain a - CapabilityStatement resource. Thanks to Michael Lawley - for the pull request! - - - Fix a crash in JPA server when searching using an _include if _include targets are - external references (and therefore can't be loaded - by the server). Thanks to Hannes Ulrich for reporting! - - - HAPI FHIR CLI failed to delete a file when uploading - example resources while running under Windows. - - - Server should reject update if the resource body - does not contain an ID, or the ID does not match - the request URL. Thanks to Jim Steel for reporting! - - - Web Testing UI's next and previous buttons for paging - through paged results did not work after the migration - to using Thymeleaf 3. Thanks to GitHub user @gsureshkumar - for reporting! - - - When parsing invalid enum values in STU3, - report errors through the parserErrorHandler, - not by throwing an exception. Thanks to - Michael Lawley for the pull request! - - - When parsing DSTU3 resources with enumerated - types that contain invalid values, the parser will now - invoke the parserErrorHandler. For example, when parsing - {"resourceType":"Patient", "gender":"foo"} - ]]> - the previous behaviour was to throw an InvalidArgumentException. - Now, the parserErrorHandler is invoked. In addition, thw - LenientErrorHandler has been modified so that this one case - will result in a DataFormatException. This has the effect - that servers which receive an invalid enum velue will return - an HTTP 400 instead of an HTTP 500. Thanks to Jim - Steel for reporting! - - - DSTU3 context now pulls the FHIR version from the actual - model classes. Thanks to Michael Lawley for the pull request! - - - Enhancements to the tinder-plugin's generic template features - of the generate-multi-files and generate-single-file - Maven goals as well as the Ant hapi-tinder task. -
          -
        • Provides the full Tinder data model by adding composites, valuesets, and profiles to resourcesw.
        • -
        • Supports generating files for resources, composites, valuesets, and profiles
        • -
        • Supports Velocimacro files outside the tinder-plugin JAR
        • -
        • Provides filename prefix as well as suffix properties
        • -
        • Can specify any of the Velocity configuration parameters such as - macro.provide.scope.control which allows safe macro recursion
        • -
        • Templates can now drill down into the referenced children for a ResourceBlockCopy
        • -
        • Normalization of properties across all three generic tasks
        • -
        - ]]> -
        - - Fix ordering of validator property handling when an element - has a name that is similar to a shorter name[x] style name. - Thanks to CarthageKing for the pull request! - - - Add a docker configuration to the hapi-fhir-jpaservr-example - module. Thanks to Gijsbert van den Brink for the pull request! - - - Add utility constructors to MoneyDt. Thanks to James Ren for the - contribution! - - - AuthorizationInterceptor was failing to allow read requests to pass - when a rule authorized those resources by compartment. Thanks to - GitHub user @mattiuusitalo for reporting and supplying - a test case! - - - Correct a typo in client - IHttpRequest]]> class: "bufferEntitity" should be "bufferEntity". - - - ErrorHandler is now called (resulting in a warning by default, but can also be an exception) when arsing - JSON if - the resource ID is not a JSON string, or an object is found where an array is expected (e.g. repeating - field). Thanks - to Jenni Syed of Cerner for providing a test case! - - - Fix Web Testing UI to be able to handle STU3 servers which - return CapabilityStatement instead of the previously used - "Conformance" resource - - - CLI example uploader couldn't find STU3 examples after CI server - was moved to build.fhir.org - - - Fix issue in JPA subscription module that prevented purging stale - subscriptions when many were present on Postgres - - - Server interceptor methods were being called twice unnecessarily - by the JPA server, and the DaoConfig interceptor registration - framework was not actually useful. Thanks to GitHub user - @mattiuusitalo for reporting! - - - AuthorizationInterceptor on JPA server did not correctly - apply rules on deleting resources in a specific compartment - because the resource metadata was stripped by the JPA server - before the interceptor could see it. Thanks to - Eeva Turkka for reporting! - - - JPA server exported CapabilityStatement includes - double entries for the _id parameter and uses the - wrong type (string instead of token). Thanks to - Robert Lichtenberger for reporting! - - - Custom resource types which extend Binary must not - have declared extensions since this is invalid in - FHIR (and HAPI would just ignore them anyhow). Thanks - to Thomas S Berg for reporting! - - - Standard HAPI zip/tar distributions did not include the project - sources and JavaDoc JARs. Thanks to Keith Boone for pointing - this out! - - - Server AuthorizationInterceptor always rejects history operation - at the type level even if rules should allow it. - - - JPA server terminology service was not correctly validating or expanding codes - in SNOMED CT or LOINC code systems. Thanks to David Hay for reporting! - - - Attempting to search for an invalid resource type (e.g. GET base/FooResource) should - return an HTTP 404 and not a 400, per the HTTP spec. Thanks to - GitHub user @CarthageKing for the pull request! - - - When parsing a Bundle containing placeholder fullUrls and references - (e.g. "urn:uuid:0000-0000") the resource reference targets did not get - populated with the given resources. Note that as a part of this - change, IdType and IdDt]]> have been modified - so that when parsing a placeholder ID, the complete placeholder including the - "urn:uuid:" or "urn:oid:" prefix will be placed into the ID part. Previously, - the prefix was treated as the base URL, which led to strange behaviour - like the placeholder being treated as a real IDs. Thanks to GitHub - user @jodue for reporting! - - - Declared extensions with multiple type() options listed in the @Child - annotation caused a crash on startup. Now this is supported. - - - STU3 XHTML parser for narrative choked if the narrative contained - an &rsquot;]]> entity string. - - - When parsing a quantity parameter on the server with a - value and units but no system (e.g. - GET [base]/Observation?value=5.4||mg]]>) - the unit was incorrectly treated as the system. Thanks to - @CarthageKing for the pull request! - - - Correct a typo in the JPA ValueSet ResourceProvider which prevented - successful operation under Spring 4.3. Thanks to - Robbert van Waveren for the pull request! - - - Deprecate the method - ICompositeElement#getAllPopulatedChildElementsOfType(Class)]]> - as it is no longer used by HAPI and is just an annoying step - in creating custom structures. Thanks to Allan Bro Hansen - for pointing this out. - - - CapturingInterceptor did not buffer the response meaning - that in many circumstances it did not actually capture - the response. Thanks to Jenny Syed of Cerner for - the pull request and contribution! - -
        - - - STU3 structure definitions have been updated to the - STU3 latest definitions (1.7.0 - SVN 10129). In - particular, this version supports the new CapabilityStatement - resource which replaces the previous Conformance - resource (in order to reduce upgrade pain, both resource - types are included in this version of HAPI) - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • spring-data-orm (JPA): 1.10.2 -> 1.10.4
      • - - ]]> -
        - - Fix a fairly significant issue in JPA Server when using the - DatabaseBackedPagingProvider]]>: When paging over the results - of a search / $everything operation, under certain circumstances resources may be missing from the last page - of results - that is returned. Thanks to David Hay for reporting! - - - Client, Server, and JPA server now support experimental support - for - - using the XML Patch and JSON Patch syntax as explored during the - September 2016 Baltimore Connectathon. See - this wiki page]]> - for a description of the syntax. - ]]> - Thanks to Pater Girard for all of his help during the connectathon - in implementing this feature! - - - Android library now uses OkHttp client by default instead - of Apache HttpClient. This should lead to much simpler - support for Android in the future. - - - Both client and server now use the new STU3 mime types by default - if running in STU3 mode (in other words, using an STU3 - FhirContext). - - - In server, when returning a list of resources, the server sometimes failed to add - _include]]> resources to the response bundle if they were - referred to by a contained resource. Thanks to Neal Acharya for reporting! - - - Fix regression in web testing UI where "prev" and "next" buttons don't work - when showing a result bundle - - - JPA server should not attempt to resolve built-in FHIR StructureDefinitions from the - database (this causes a significant performance hit when validating) - - - BanUnsupportedHttpMethodsInterceptor was erroring out when a client - attempts HTTP HEAD requests - - - Conditional URLs in JPA server (e.g. for delete or update) did not support the - _has]]> parameter - - - Remove Maven dependency on Saxon library, as it is not actually used. Thanks - to Lem Edmondson for the suggestion! - - - Times before 1970 with fractional milliseconds were parsed incorrectly. Thanks - to GitHub user @CarthageKing for reporting! - - - Prevent crash in parser when parsing resource - with multiple profile declarations when - default type for profile is used. Thanks to - Filip Domazet for the pull request! - - - STU3 servers were adding the old MimeType - strings to the - Conformance.format]]> - part of the generated server conformance - statement - - - When performing an update using the client on a resource that - contains other resources (e.g. Bundle update), all child resources in the - parent bundle were incorrectly given the ID of the parent. Thanks - to Filip Domazet for reporting! - - - STU clients now use an Accept header which - indicates support for both the old MimeTypes - (e.g. application/xml+fhir]]>) - and the new MimeTypes - (e.g. application/fhir+xml]]>) - - - JPA server now sends correct - HTTP 409 Version Conflict]]> - when a - DELETE fails because of constraint issues, instead of - HTTP 400 Invalid Request]]> - - - Server history operation did not populate the Bundle.entry.request.url - field, which is required in order for the bundle to pass validation. - Thanks to Richard Ettema for spotting this! - - - Add a new method to the server interceptor framework which will be - called after all other processing is complete (useful for performance - tracking). The server LoggingInterceptor has been switched to using this - method which means that log lines will be created when processing is finished, - instead of when it started. - - - STU3 clients were not sending the new mimetype values in the - Content-Type]]> header. Thanks to - Claude Nanjo for pointing this out! - - - JAX-RS server was not able to handle the new mime types defined - in STU3 - - - JPA server did not handle custom types when being called - programatically (I.e. not through HTTP interface). Thanks to - Anthony Mei for pointing this out! - - - CLI was not correctly able to upload DSTU2 examples to any server - - - STU3 validator has been upgrated to include fixes made since the - 1.6.0 ballot - - - Prevent JPA server from creating a bunch of - FhirContext objects for versions of FHIR that - aren't actually being used - - - XhtmlNode.equalsDeep() contained a bug which caused resources - containing a narrative to always return - false]]> for STU3 - Resource#equalsDeep()]]>. Thanks to - GitHub user @XcrigX for reporting! - - - JPA server did not correctly process searches for chained parameters - where the chain passed across a field that was a choice between a - reference and a non-reference type (e.g. - MedicationAdministration.medication[x]]]>. - Thanks to GitHub user @Crudelus for reporting! - - - Handle parsing an extension without a URL more gracefully. In HAPI FHIR 2.0 this caused - a NullPointerException to be thrown. Now it will trigger a warning, or throw a - DataFormatException if the StrictErrorHandler is configured on the parser. - - - Calling a HAPI server URL with a chain on a parameter that shouldn't accept - chains (e.g. - GET [base]/Patient?name.foo=smith]]>) - did not return an error and instead just ignored the chained part - and treated the parameter as though it did not have the chain. This - led to confusing and potentially unsafe behaviour. This has been - corrected to return an error to the client. Thanks to - Kevin Tallevi for finding this! - - - Fix #411 - Searching by POST [base]/_search]]> with urlencoded parameters doesn't work - correctly if - interceptors are accessing the parameters and there is are also - parameters on the URL. Thanks to Jim Steel for reporting! - - - Fluent client can now return types other than Parameters - when invoking operations. - - - JPA server shouldn't report a totalCount in Bundle of "-1" when - there are no results - - - JPA server was not correctly normalizing strings with non-latin characters - (e.g. Chinese chars). Thanks to GitHub user @YinAqu for reporting and providing - some great analysis of the issue! - - - Add a new method to ReferenceClientParam which allows you to - pass in a number of IDs by a collection of Strings. Thanks to - Thomas Andersen for the pul request! - - - When encoding a resource in JSON where the resource has - an extension with a value where the value is a reference to a - contained resource, the reference value (e.g. "#1") did not - get serialized. Thanks to GitHub user @fw060 for reporting! - - - ResponseHighlighterInterceptor now pretty-prints responses - by default unless the user has explicitly requested - a non-pretty-printed response (ie. - using ?_pretty=false]]>. Thanks to - Allan Brohansen and Jens Villadsen for the suggestion! - - - Add a new JSON library abstraction layer to the JSON parser. - This contribution shouldn't have any end-user impact but does - make it easier to use the JSON parser to generate custom structures - for other purposes, and should allow us to support RDF more - easily at some point. Thanks to Bill Denton for the pull - request and the contribution! - - - DSTU1 Bundle encoder did not include the Bundle entry author in - the generated bundle. Thanks to Hannes Venter for the pull - request and contribution! - - - Remove unused field (myIsContained) from ResourceTable - in JPA server. - - - AuthorizationInterceptor is now a bit more aggressive - at blocking read operations, stopping them on the - way in if there is no way they will be accepted - to the resource check on the way out. In addition - it can now be configured to allow/deny operation - invocations at the instance level on any - instance of a given type - - - STU3 servers were incorrectly returning the - Content-Location]]> - header instead of the - Content]]> - header. The former has been removed from the - FHIR specification in STU3, but the - latter got removed in HAPI's code base. - Thanks to Jim Steel for reporting! - - - Correct several documentation issues. Thanks to Vadim Peretokin - for the pull requests! - - - Remove an unneccesary database flush - from JPA persistence operations - - - Add method to fluent client to allow OR search across several - profiles. Thanks to Thomas Andersen for the pull request! - -
        - - - JSON parsing in HAPI FHIR has been switched from using JSR353 (javax.json) to - using Google Gson. For this reason we are bumping the major release number to - 2.0. Theoretically this should not affect projects in any major way, but Gson - does have subtle differences. Two differences which popped up a fair bit in - our own testing: - -
          - A space is placed after the : in keys, e.g. what was previously - encoded as "resourceType":"Patient" is now encoded - as "resourceType": "Patient" (this broke a number of - our unit tests with hardcoded resource definitions) -
        -
          - Trailing content after a valid json resource is rejected by - Gson (it was ignored by the Glassfish parser we were previously - using even though it was invalid) -
        - - ]]> -
        - - STU3 structure definitions have been updated to the - STU3 ballot candidate versions (1.6.0 - SVN 9663) - - - Both client and server now support the new Content Types decided in - FHIR #10199]]> - . -
        ]]> - This means that the server now supports - application/fhir+xml and application/fhir+json]]> - in addition to the older style - application/xml+fhir and application/json+fhir]]>. - In order to facilitate migration by implementors, the old style remains the default - for now, but the server will respond using the new style if the request contains it. The - client now uses an Accept]]> header value which requests both - styles with a preference given to the new style when running in DSTU3 mode. -
        ]]> - As a part of this change, the server has also been enhanced so that if a request - contains a Content-Type header but no Accept header, the response will prefer the - encoding specified by the Content-Type header. -
        - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Logback (used in sample projects): 1.1.5 -> 1.1.7
      • -
      • Phloc Commons (used by schematron validator): 4.4.4 -> 4.4.5
      • -
      • Commons-IO: 2.4 -> 2.5
      • -
      • Apache HTTPClient: 4.5.1 -> 4.5.2
      • -
      • Apache HTTPCore: 4.4.4 -> 4.4.5
      • -
      • Jersey (JAX-RS tests): 2.22.2 -> 2.23.1
      • -
      • Spring (JPA, Web Tester): 4.3.0 -> 4.3.1
      • - -
      • Hibernate Search (JPA): 5.5.2 -> 5.5.4
      • -
      • Thymeleaf (Narrative Generator / Web Tester): 2.1.4 ->3.0.1
      • - - ]]> -
        - - - Fix issue in DSTU1 Bundle parsing where unexpected elements in the bundle resulted in a failure - to parse. - - - DSTU2 QuestionnaireResponse validator failed with an exception if the - QuestionnaireResponse contained certain groups with no content - - - Fluent client should ignore parameter values which are null instead of including - them as ?foo=null]]> - - - When using _elements]]> parameter on server, the server was not - automatically adding the SUBSETTED]]> tag as it should - - - JPA server should now automatically detect - if Hibernate Search (Lucene) is configured to be - disabled and will not attempt to use it. This - prevents a crash for some operations. - - - A new server interceptor "BanUnsupprtedHttpMethodsInterceptor" has been added - which causes the server to return an HTTP 405 if an unsupported HTTP - verb is received from the client - - - Fix an issue where resource IDs were not correctly set when using - DSTU2 HL7org structures with the JAX-RS module. Thanks to Carlo Mion - for the pull request! - - - hapi-fhir-testpage-overlay project contained an unneccesary - dependency on hapi-fhir-jpaserver-base module, which resulted in - projects using the overlay having a large number of unnneded - JARs included - - - It is not possible to configure both the parser and the context to - preserve versions in resource references (default behaviour is to - strip versions from references). Thanks to GitHub user @cknaap - for the suggestion! - - - Tag#setCode(String)]]> did not actually set the code it was supposed to - set. Thanks to Tim Tschampel for reporting! - - - JPA server's /Bundle]]> endpoint cleared - the Bundle.entry.fullUrl]]> field on stored - bundles, resulting in invalid content being saved. Thanks to Mirjam - Baltus for reporting! - - - JPA server now returns HTTP 200 instead of HTTP 404 for - conditional deletes which did not find any matches, - per FHIR-I decision. - - - Client that declares explicitly that it is searching/reading/etc for - a custom type did not automatically parse into that type. - - - Allow servers to specify the authentication realm of their choosing when - throwing an AuthenticationException. Thanks to GitHub user @allanbrohansen - for the suggestion! - - - Add a new client implementation which uses the - OkHttp]]> - library as the HTTP client implementation (instead of Apache HttpClient). - This is particularly useful for Android (where HttpClient is a pain) but - could also be useful in other places too. - Thanks to Matt Clarke of Orion Health for the contribution! - - - Fix a regression when parsing resources that have contained - resources, where the reference in the outer resource which - links to the contained resource sometimes did does not get - populated with the actual target resource instance. Thanks to - Neal Acharya for reporting! - - - hapi-fhir-cli upload-terminology command now has an argument - "-b FOO" that lets you add an authorization header in the form - Authorization: Bearer FOO]]> - - - Parser failed to successfully encode a custom resource - if it contained custom fields that also used custom - types. Thanks to GitHub user @sjanic for reporting! - - - Inprove handling of _text and _content searches in JPA server to do better - matching on partial strings - - - Servers in STU3 mode will now ignore any ID or VersionID found in the - resource body provided by the client when processing FHIR - update]]> operations. This change has been made - because the FHIR specification now requires servers to ignore - these values. Note that as a result of this change, resources passed - to @Update]]> methods will always have - null]]> ID - - - Add new methods to - AuthorizationInterceptor]]> - which allow user code to declare support for conditional - create, update, and delete. - - - When encoding a resource with a reference to another resource - that has a placeholder ID (e.g. urn:uuid:foo), the urn prefix - was incorrectly stripped from the reference. - - - Servers for STU3 (or newer) will no longer include a - Location:]]> header on responses for - read]]> operations. This header was - required in earlier versions of FHIR but has been removed - from the specification. - - - Fix NullPointerException when encoding an extension containing CodeableConcept - with log level set to TRACE. Thanks to Bill Denton for the report! - - - Add two new methods to the parser error handler that let users trap - invalid contained resources with no ID, as well as references to contained - resource that do not exist. - - - Improve performance when parsing resources containing contained resources - by eliminating a step where references were woven twice - - - Parser failed to parse resources containing an extension with a value type of - "id". Thanks to Raphael Mäder for reporting! - - - When committing a transaction in JPA server - where the transaction contained placeholder IDs - for references between bundles, the placeholder - IDs were not substituted with viewing - resources using the _history operation - - - HAPI root pom shouldn't include animal-sniffer plugin, - since that causes any projects which extend this to - be held to Java 6 compliance. - -
        - - - Performance has been improved for the initial FhirContext - object creation by avoiding a lot of unnecessary reflection. HAPI FHIR - 1.5 had a regression compared to previous releases - and this has been corrected, but other improvements have been - made so that this release is faster than previous releases too. -
        ]]> - In addition, a new "deferred scan" mode has been implemented for - even faster initialization on slower environments (e.g. Android). - See the performance documentation]]> - for more information. -
        ]]> - The following shows our benchmarks for context initialization across several - versions of HAPI: - -
      • Version 1.4: 560ms
      • -
      • Version 1.5: 800ms
      • -
      • Version 1.6: 340ms
      • -
      • Version 1.6 (deferred mode): 240ms
      • - - ]]> -
        - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Spring (JPA, Web Tester): 4.2.5 -> 4.3.0
      • -
      • Spring-Data (JPA): 1.9.2 -> 1.10.1
      • - -
      • Hibernate Search (JPA): 5.5.2 -> 5.5.3
      • -
      • Jetty (CLI): 9.3.9 -> 9.3.10
      • - - ]]> -
        - - Remove some clases that were deprecated over a year ago and have - suitable replacements: - -
      • QualifiedDateParam has been removed, but DateParam may be used instead
      • -
      • PathSpecification has been removedm but Include may be used instead
      • - - ]]> -
        - - ResponseValidatingInterceptor threw an InternalErrorException (HTTP 500) for operations - that do not return any content (e.g. delete). Thanks to Mohammad Jafari for reporting! - - - REST server now throws an HTTP 400 instead of an HTTP 500 if an operation which takes - a FHIR resource in the request body (e.g. create, update) contains invalid content that - the parser is unable to parse. Thanks to Jim Steel for the suggestion! - - - Deprecate fluent client search operations without an explicit declaration of the - bundle type being used. This also means that in a client - .search()]]> - operation, the - .returnBundle(Bundle.class)]]> - needs to be the last statement before - .execute()]]> - - - Server now respects the parameter _format=application/xml+fhir"]]> - which is technically invalid since the + should be escaped, but is likely to be used. Also, - a parameter of _format=html]]> can now be used, which - forces SyntaxHighlightingInterceptor to use HTML even - if the headers wouldn't otherwise trigger it. - Thanks to Jim Steel for reporting! - - - Improve performance when parsing large bundles by fixing a loop over all of the - entries inthe bundle to stitch together cross-references, which was happening once - per entry instead of once overall. Thanks to Erick on the HAPI FHIR Google Group for - noticing that this was an issue! - - - JSON parser no longer allows the resource ID to be specified in an element called "_id" - (the correct one is "id"). Previously _id was allowed because some early FHIR examples - used that form, but this was never actually valid so it is now being removed. - - - JPA server now allows "forced IDs" (ids containing non-numeric, client assigned IDs) - to use the same logical ID part on different resource types. E.g. A server may now have - both Patient/foo and Obervation/foo on the same server.
        ]]> - Note that existing databases will need to modify index "IDX_FORCEDID" as - it is no longer unique, and perform a reindexing pass. -
        - - When serializing/encoding custom types which replace exsting choice fields by - fixing the choice to a single type, the parser would forget that the - field was a choice and would use the wrong name (e.g. "abatement" instead of - "abatementDateType"). Thanks to Yaroslav Kovbas for reporting and - providing a unit test! - - - JPA server transactions sometimes created an incorrect resource reference - if a resource being saved contained references that had a display value but - not an actual reference. Thanks to David Hay for reporting! - - - When performing a REST Client create or update with - Prefer: return=representation]]> set, - if the server does not honour the Prefer header, the client - will automatically fetch the resource before returning. Thanks - to Ewout Kramer for the idea! - - - DSTU3 structures now have - setFoo(List)]]> - and - setGetFooFirstRep()]]> - methods, bringing them back to parity with the HAPI - DSTU2 structures. Thanks to Rahul Somasunderam and - Claude Nanjo for the suggestions! - - - JPA server has now been refactored to use the - new FluentPath search parameter definitions - for DSTU3 resources. - - - RequestValidatingInterceptor and ResponseValidatingInterceptor - both have new method setIgnoreValidatorExceptions]]> - which causes validator exceptions to be ignored, rather than causing - processing to be aborted. - - - LoggingInterceptor on server has a new parameter - ${requestBodyFhir}]]> which logs the entire request body. - - - JAX-RS server module now supports DSTU3 resources (previously it only supported DSTU2). Thanks - to Phillip Warner for implementing this, and providing a pull request! - - - Generated conformance statements for DSTU3 servers did not properly reference their - OperationDefinitions. Thanks - to Phillip Warner for implementing this, and providing a pull request! - - - Properly handle null arrays when parsing JSON resources. Thanks to Subhro for - fixing this and providing a pull request! - - - STU3 validator failed to validate codes where the - code was a child code within the code system that contained it - (i.e. not a top level code). Thanks to Jon - Zammit for reporting! - - - Restore the setType method in the DSTU1 Bundle - class, as it was accidentally commented out. Thanks - to GitHub user @Virdulys for the pull request! - - - JPA server now supports composite search parameters - where the type of the composite parameter is - a quantity (e.g. Observation:component-code-component-value-quantity) - - - Remove the Remittance resource from DSTU2 - structures, as it is not a real resource and - was causing issues with interoperability - with the .NET client. - - - CLI tool cache feature (-c) for upload-example task sometimes failed - to write cache file and exited with an exception. - - - Fix error message in web testing UI when loading pages in a search - result for STU3 endpoints. - - - When encoding JSON resource, the parser will now always - ensure that XHTML narrative content has an - XHTML namespace declaration on the first - DIV tag. This was preventing validation for - some resources using the official validator - rules. - - - Server failed to invoke operations when the name - was escaped (%24execute instead of $execute). - Thanks to Michael Lawley for reporting! - - - JPA server transactions containing a bundle that has multiple entries - trying to delete the same resource caused a 500 internal error - - - JPA module failed to index search parameters that mapped to a Timing datatype, - e.g. CarePlan:activitydate - - - Add a new option to the CLI run-server command called --lowmem]]>. - This option disables some features (e.g. fulltext search) in order to allow the - server to start in memory-constrained environments (e.g Raspberry Pi) - - - When updating a resource via an update operation on the server, if the ID of the - resource is not present in the resource body but is present on the URL, this will - now be treated as a warning instead of as a failure in order to be a bit more - tolerant of errors. If the ID is present in the body but does not agree with the - ID in the URL this remains an error. - - - Server / JPA server date range search params (e.g. Encounter:date) now treat - a single date with no comparator (or the eq comparator) as requiring that the - value be completely contained by the range specified. Thanks to Chris Moesel - for the suggestion. - - - In server, if a parameter was annotated with the annotation, the - count would not appear in the self/prev/next links and would not actually be applied - to the search results by the server. Thanks to Jim Steele for letting us know! - - - Conditional update on server failed to process if the conditional URL did not have any - search parameters that did not start with an underscore. E.g. "Patient?_id=1" failed - even though this is a valid conditional reference. - - - JPA server can now be configured to allow external references (i.e. references that - point to resources on other servers). See - JPA Documentation]]> for information on - how to use this. Thanks to Naminder Soorma for the suggestion! - - - When posting a resource to a server that contains an invalid value in a boolean field - (e.g. Patient with an active value of "1") the server should return an HTTP 400, not - an HTTP 500. Thanks to Jim Steel for reporting! - - - Enable parsers to parse and serialize custom resources that contain custom datatypes. - An example has been added which shows how to do this - here]]> - - - JSON parser was incorrectly encoding resource language attribute in JSON as an - array instead of a string. Thanks to David Hay for reporting! - - - Sébastien Rivière contributed an excellent pull request which adds a - number of enhancements to JAX-RS module: - -
      • Enable the conditional update and delete
      • -
      • Creation of a bundle provider, and support of the @Transaction
      • -
      • Bug fix on the exceptions handling as some exceptions throw outside bean context were not intercept.
      • -
      • Add the possibility to have the stacktrace in the jaxrsException
      • - - ]]> -
        - - FhirTerser.cloneInto method failed to clone correctly if the source - had any extensions. Thanks to GitHub user @Virdulys for submitting and - providing a test case! - - - Update DSTU2 InstanceValidator to latest version from upstream - - - Web Testing UI was not able to correctly post an STU3 transaction - - - DateTime parser incorrectly parsed times where more than 3 digits of - precision were provided on the seconds after the decimal point - - - Improve error messages when the $validate operation is called but no resource - is actually supplied to validate - - - DSTU2+ servers no longer return the Category header, as this has been - removed from the FHIR specification (and tags are now available in the - resource body so the header was duplication/wasted bandwidth) - - - Create and Update operations in server did not - include ETag or Last-Modified headers even though - the spec says they should. Thanks to Jim Steel for - reporting! - - - Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub - user @euz1e4r for - reporting! - - - QuantityClientParam#withUnit(String) put the unit into the system part of the - parameter value - - - Fluent client searches with date parameters were not correctly using - new prefix style (e.g. gt) instead of old one (e.g. >) - - - Some built-in v3 code systems for STU3 resources were missing - certain codes, which caused false failures when validating - resources. Thanks to GitHub user @Xoude for reporting! - - - Some methods on DSTU2 model structures have JavaDocs that - incorrectly claim that the method will not return null when - in fact it can. Thanks to Rick Riemer for reporting! - - - ResponseHighlightingInterceptor has been modified based on consensus - on Zulip with Grahame that requests that have a parameter of - _format=json]]> or - _format=xml]]> will output raw FHIR content - instead of HTML highlighting the content as they previously did. - HTML content can now be forced via the (previously existing) - _format=html]]> or via the two newly added - values - _format=html/json]]> and - _format=html/xml]]>. Because of this - change, the custom - _raw=true]]> mode has been deprecated and - will be removed at some point. - - - Operation definitions (e.g. for $everything operation) in the generated - server conformance statement should not include the $ prefix in the operation - name or code. Thanks to Dion McMurtrie for reporting! - - - Server generated OperationDefinition resources did not validate - due to some missing elements (kind, status, etc.). - Thanks to - Michael Lawley for reporting! - - - Operations that are defined on multiple resource provider types with - the same name (e.g. "$everything") are now automatically exposed by the server - as separate OperationDefinition resources per resource type. Thanks to - Michael Lawley for reporting! - - - OperationDefinition resources generated automatically by the server for operations - that are defined within resource/plain providers incorrectly stated that - the maximum cardinality was "*" for non-collection types with no explicit - maximum stated, which is not the behaviour that the JavaDoc on the - annotation describes. Thanks to Michael Lawley - for reporting! - - - Server parameters annotated with - @Since]]> - or - @Count]]> - which are of a FHIR type such as IntegerDt or DateTimeType will - now be set to null if the client's URL does not - contain this parameter. Previously they would be populated - with an empty instance of the FHIR type, which was inconsistent with - the way other server parameters worked. - - - Server now supports the _at parameter (including multiple repetitions) - for history operation - - - - AuthorizationInterceptor can now allow or deny requests to extended - operations (e.g. $everything) - - - DecimalType used BigDecimal constructor instead of valueOf method to - create a BigDecimal from a double, resulting in weird floating point - conversions. Thanks to Craig McClendon for reporting! - - - Remove the depdendency on a method from commons-lang3 3.3 which was - causing issues on some Android phones which come with an older version - of this library bundled. Thanks to Paolo Perliti for reporting! - - - Parser is now better able to handle encoding fields which have been - populated with a class that extends the expected class - - - When declaring a child with - order=Child.REPLACE_PARENT]]> - the serialized form still put the element at the - end of the resource instead of in the correct - order - - - Fix STU3 JPA resource providers to allow validate operation - at instance level - -
        - - - Security Fix: XML parser was vulnerable to XXE (XML External Entity) - processing, which could result in local files on disk being disclosed. - See this page]]> - for more information. - Thanks to Jim Steel for reporting! - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Hibernate (JPA, Web Tester): 5.0.7 -> 5.1.0
      • -
      • Spring (JPA, Web Tester): 4.2.4 -> 4.2.5
      • -
      • SLF4j (All): 1.7.14 -> 1.7.21
      • - - ]]> -
        - - Support comments when parsing and encoding both JSON and XML. Comments are retrieved - and added to the newly created methods - IBase#getFormatCommentsPre() and - IBase#getFormatCommentsPost() - - - Added options to the CLI upload-examples command which allow it to cache - the downloaded content file, or use an arbitrary one. Thanks to Adam Carbone - for the pull request! - - - REST search parameters with a prefix/comparator had not been updated to use - the DSTU2 style prefixes (gt2011-01-10) instead of the DSTU1 style prefixes - (>2011-01-01). The client has been updated so that it uses the new prefixes - if the client has a DSTU2+ context. The server has been updated so that it now - supports both styles. -
        ]]> - As a part of this change, a new enum called - ParamPrefixEnum]]> - has been introduced. This enum replaces the old - QuantityCompararatorEnum]]> - which has a typo in its name and can not represent several new prefixes added since - DSTU1. -
        - - JPA server number and quantity search params now follow the rules for the - use of precision in search terms outlined in the - search page]]> of the - FHIR specification. For example, previously a 1% tolerance was applied for - all searches (10% for approximate search). Now, a tolerance which respects the - precision of the search term is used (but still 10% for approximate search). - - - Fix a failure starting the REST server if a method returns an untyped List, which - among other things prevented resource provider added to the server - as CDI beans in a JBoss enviroment. Thanks to GitHub user fw060 (Fei) for - reporting and figuring out exactly why this wasn't working! - - - JPA server now supports :above and :below qualifiers on URI search params - - - Add optional support (disabled by default for now) to JPA server to support - inline references containing search URLs. These URLs will be resolved when - a resource is being created/updated and replaced with the single matching - resource. This is being used as a part of the May 2016 Connectathon for - a testing scenario. - - - The server no longer adds a - WWW-Authenticate]]> - header to the response if any resource provider code throws an - AuthenticationException]]>. This header is - used for interactive authentication, which isn't generally - appropriate for FHIR. We added code to add this header a long time - ago for testing purposes and it never got removed. Please let us - know if you need the ability to add this header automatically. Thanks - to Lars Kristian Roland for pointing this out. - - - In the client, the create/update operations on a Binary resource - (which use the raw binary's content type as opposed to the FHIR - content type) were not including any request headers (Content-Type, - User-Agent, etc.) Thanks to Peter Van Houte of Agfa Healthcare for - reporting! - - - Handling of Binary resources containing embedded FHIR resources for - create/update/etc operations has been corrected per the FHIR rules - outlined at - Binary Resource]]> - in both the client and server. -
        ]]> - Essentially, if the Binary contains something - that isn't FHIR (e.g. an image with an image content-type) the - client will send the raw data with the image content type to the server. The - server will place the content type and raw data into a Binary resource instance - and pass those to the resource provider. This part was already correct previous - to 1.5. -
        ]]> - On the other hand, if the Binary contains a FHIR content type, the Binary - is now sent by the client to the server as a Binary resource with a FHIR content-type, - and the embedded FHIR content is contained in the appropriate fields. The server - will pass this "outer" Binary resource to the resource provider code. -
        - - The RequestDetails and ActionRequestDetails objects which are passed to - server interceptor methods and may also be used as server provider method - arguments now has a new method - Map<String, String> getUserData() - ]]> - which can be used to pass data and objects between interceptor methods to - to providers. This can be useful, for instance, if an authorization - interceptor wants to pass the logged in user's details to other parts - of the server. - - - IServerInterceptor#incomingRequestPreHandled() is called - for a @Validate method, the resource was not populated in the - ActionRequestDetails argument. Thanks to Ravi Kuchi for reporting! - ]]> - - - [baseUrl]/metadata with an HTTP method - other than GET (e.g. POST, PUT) should result in an HTTP 405. Thanks to - Michael Lawley for reporting! - ]]> - - - Fix a server exception when trying to automatically add the profile tag - to a resource which already has one or more profiles set. Thanks to - Magnus Vinther for reporting! - - - QuantityParam parameters being used in the RESTful server were ignoring - the - :missing]]> - qualifier. Thanks to Alexander Takacs for reporting! - - - Annotation client failed with an exception if the response contained - extensions on fields in the resonse Bundle (e.g. Bundle.entry.search). - Thanks to GitHub user am202 for reporting! - - - Primitive elements with no value but an extension were sometimes not - encoded correctly in XML, and sometimes not parsed correctly in JSON. - Thanks to Bill de Beaubien for reporting! - - - The Web Testing UI has long had an issue where if you click on a button which - navigates to a new page (e.g. search, read, etc) and then click the back button - to return to the original page, the button you clicked remains disabled and can't - be clicked again (on Firefox and Safari). This is now fixed. Unfortunately the fix means that the - buttom will no longer show a "loading" spinner, but there doesn't seem to - be another way of fixing this. Thanks to Mark Scrimshire for reporting! - - - Extensions found while parsing an object that doesn't support extensions are now - reported using the IParserErrorHandler framework in the same way that - other similar errors are handled. This allows the parser to be more lenient - when needed. - - - Improve error message if incorrect type is placed in a list field in the data model. Java - uses generics to prevent this at compile time, but if someone is in an environment without - generics this helps improve the error message at runtime. Thanks to Hugo Soares for - suggesting. - - - Prevent an unneeded warning when parsing a resource containing - a declared extension. Thanks to Matt Blanchette for reporting! - - - Web Tester UI did not invoke VRead even if a version ID was specified. Thanks - to Poseidon for reporting! - - - Per discussion on the FHIR implementer chat, the JPA server no - longer includes _revinclude matches in the Bundle.total count, or the - page size limit. - - - JPA server now persists search results to the database in a new table where they - can be temporaily preserved. This makes the JPA server much more scalable, since it - no longer needs to store large lists of pages in memory between search invocations. -
        ]]> - Old searches are deleted after an hour by default, but this can be changed - via a setting in the DaoConfig. -
        - - JPA servers' resource version history mechanism - has been adjusted so that the history table - keeps a record of all versions including the - current version. This has the very helpful - side effect that history no longer needs to be - paged into memory as a complete set. Previously - history had a hard limit of only being able to - page the most recent 20000 entries. Now it has - no limit. - - - JPA server returned the wrong Bundle.type value (COLLECTION, should be SEARCHSET) - for $everything operation responses. Thanks to Sonali Somase for reporting! - - - REST and JPA server should reject update requests where the resource body does not - contain an ID, or contains an ID which does not match the URL. Previously these - were accepted (the URL ID was trusted) which is incorrect according to the - FHIR specification. Thanks to GitHub user ametke for reporting! -
        ]]> - As a part of this change, server error messages were also improved for - requests where the URL does not contain an ID but needs to (e.g. for - an update) or contains an ID but shouldn't (e.g. for a create) -
        - - When fields of type BoundCodeDt (e.g. Patient.gender) - are serialized and deserialized using Java's native - object serialization, the enum binder was not - serialized too. This meant that values for the - field in the deserialized object could not be - modified. Thanks to Thomas Andersen for reporting! - - - REST Server responded to HTTP OPTIONS requests with - any URI as being a request for the server's - Conformance statement. This is incorrect, as only - a request for OPTIONS [base url]]]> should be treated as such. Thanks to Michael - Lawley for reporting! - - - REST annotation style client was not able to handle extended operations - ($foo) where the response from the server was a raw resource instead - of a Parameters resource. Thanks to Andrew Michael Martin for reporting! - - - JPA server applies _lastUpdated filter inline with other searches wherever possible - instead of applying this filter as a second query against the results of the - first query. This should improve performance when searching against large - datasets. - - - Parsers have new method - setDontEncodeElements]]> - which can be used to force the parser to not encode certain elements - in a resource when serializing. For example this can be used to omit - sensitive data or skip the resource metadata. - - - JPA server database design has been adjusted - so that different tables use different sequences - to generate their indexes, resulting in more sequential - resource IDs being assigned by the server - - - Server now correctly serves up Binary resources - using their native content type (instead of as a - FHIR resource) if the request contains an accept - header containing "application/xml" as some browsers - do. - - - DSTU2 resources now have a - getMeta()]]> method which returns a - modifiable view of the resource metadata for convenience. This - matches the equivalent method in the DSTU3 structures. - - - Add a new method to FhirContext called - setDefaultTypeForProfile - ]]> - which can be used to specify that when recources are received which declare - support for specific profiles, a specific custom structures should be used - instead of the default. For example, if you have created a custom Observation - class for a specific profile, you could use this method to cause your custom - type to be used by the parser for resources in a search bundle you receive. -
        - See the documentation page on - Profiles and Extensions - for more information. - ]]> -
        - - Parsing/Encoding a custom resource type which extends a - base type sometimes caused the FhirContext to treat all future - parses of the same resource as using the custom type even when - this was not wanted. -
        ]]> - Custom structures may now be explicitly declared by profile - using the - setDefaultTypeForProfile - ]]> - method. -
        ]]> - This issue was discovered and fixed as a part of the implementation of issue #315. -
        - - Set up the tinder plugin to work as an ant task - as well as a Maven plugin, and to use external - sources. Thanks to Bill Denton for the pull - request! - - - JPA server now allows searching by token - parameter using a system only and no code, - giving a search for any tokens which match - the given token with any code. Previously the - expected behaviour for this search - was not clear in the spec and HAPI had different - behaviour from the other reference servers. - - - Introduce a JAX-RS client provider which can be used instead of the - default Apache HTTP Client provider to provide low level HTTP - services to HAPI's REST client. See - JAX-RS & Alternate HTTP Client Providers]]> - for more information. -
        ]]> - This is useful in cases where you have other non-FHIR REST clients - using a JAX-RS provider and want to take advantage of the - rest of the framework. -
        ]]> - Thanks to Peter Van Houte from Agfa for the amazing work! -
        - - Parser failed with a NPE while encoding resources if the - resource contained a null extension. Thanks to - steve1medix for reporting! - - - In generated model classes (DSTU1/2) don't - use BoundCodeDt and BoundCodeableConceptDt for - coded fields which use example bindings. Thanks - to GitHub user Ricq for reporting! - - - @Operation will now infer the maximum number of repetitions - of their parameters by the type of the parameter. Previously if - a default max() value was not specified in the - @OperationParam annotation on a parameter, the maximum - was assumed to be 1. Now, if a max value is not explicitly specified - and the type of the parameter is a basic type (e.g. StringDt) the - max will be 1. If the parameter is a collection type (e.g. List<StringDt>) - the max will be * - ]]> - - - @Operation - may now use search parameter types, such as - TokenParam and - TokenAndListParam as values. Thanks to - Christian Ohr for reporting! - ]]> - - - Add databases indexes to JPA module search index tables - for the RES_ID column on each. This should help - performance when searching over large datasets. - Thanks to Emmanuel Duviviers for the suggestion! - - - DateTimeType should fail to parse 1974-12-25+10:00 as this is not - a valid time in FHIR. Thanks to Grahame Grieve for reporting! - - - When parsing a Bundle resource, if the Bundle.entry.request.url contains a UUID - but the resource body has no ID, the Resource.id will be populated with the ID from the - Bundle.entry.request.url. This is helpful when round tripping Bundles containing - UUIDs. - - - When parsing a DSTU3 bundle, references between resources did not have - the actual resource instance populated into the reference if the - IDs matched as they did in DSTU1/2. - - - Contained resource references on DSTU3 - resources were not serialized correctly when - using the Json Parser. Thanks to GitHub user - @fw060 for reporting and supplying a patch - which corrects the issue! - - - DSTU3 model classes equalsShallow and equalsDeep both did not work - correctly if a field was null in one object, but contained an empty - object in the other (e.g. a StringType with no actual value in it). These - two should be considered equal, since they would produce the exact same - wire format.
        ]]> - Thanks to GitHub user @ipropper for reporting and providing - a test case! -
        - - JPA server now supports searching for _tag:not=[tag]]]> - which enables finding resources that to not have a given tag/profile/security tag. - Thanks to Lars Kristian Roland for the suggestion! - - - Extensions containing resource references did not get encoded correctly - some of the time. Thanks to Poseidon for reporting! - - - Parsers (both XML and JSON) encoded the first few elements of DSTU3 structures in the wrong order: - Extensions were placed before any other content, which is incorrect (several - elements come first: meta, text, etc.) - - - In server implementations, the Bundle.entry.fullUrl was not getting correctly - populated on Hl7OrgDstu2 servers. Thanks to Christian Ohr for reporting! - - - Ensure that element IDs within resources (i.e. IDs on elements other than the - resource itself) get serialized and parsed correctly. Previously, these didn't get - serialized in a bunch of circumstances. Thanks to Vadim Peretokin for reporting - and providing test cases! - - - Improve CLI error message if the tool can't bind to the requested port. Thanks - to Claude Nanjo for the suggestion! - - - Server param of _summary=text]]> did not - include mandatory elements in return as well as - the text element, even though the FHIR specification - required it. - - - Remove invalid resource type "Documentation" from DSTU2 - structures. - - - JPA server did not respect target types for search parameters. E.g. Appointment:patient has - a path of "Appointment.participant.actor" and a target type of "Patient". The search path - was being correctly handled, but the target type was being ignored. - - - RestfulServer now manually parses URL parameters instead of relying on the container's - parsed parameters. This is useful because many Java servlet containers (e.g. Tomcat, Glassfish) - default to ISO-8859-1 encoding for URLs insetad of the UTF-8 encoding specified by - FHIR. - - - ResponseHighlightingInterceptor now doesn't highlight if the request - has an Origin header, since this probably denotes an AJAX request. - -
        - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Hibernate (JPA, Web Tester): 5.0.3 -> 5.0.7
      • -
      • Springframework (JPA, Web Tester): 4.2.2 -> 4.2.4
      • -
      • Phloc-Commons (Schematron Validator): 4.3.6 -> 4.4.4
      • -
      • Apache httpclient (Client): 4.4 -> 4.5.1
      • -
      • Apache httpcore (Client): 4.4 -> 4.4.4
      • -
      • SLF4j (All): 1.7.13 -> 1.7.14
      • - - ]]> -
        - - Remove a dependency on a Java 1.7 class - (ReflectiveOperationException) in several spots in the - codebase. This dependency was accidentally introduced in - 1.3, and animal-sniffer-plugin failed to detect it (sigh). - - - Add two new server interceptors: - RequestValidatingInterceptor - and - ResponseValidatingInterceptor - ]]> - which can be used to validate incoming requests or outgoing responses using the standard FHIR validation - tools. See the - Server Validation Page - ]]> - for examples of how to use these interceptors. These intereptors have both - been enabled on the - public test page. - ]]> - - - Make IBoundCodeableConcept and IValueSetEnumBinder serializable, - fixing an issue when trying to serialize model classes containing - bound codes. Thanks to Nick Peterson for the Pull Request! - - - Introduce a JAX-RS version of the REST server, which can be used - to deploy the same resource provider implementations which work - on the existing REST server into a JAX-RS (e.g. Jersey) environment. - Thanks to Peter Van Houte from Agfa for the amazing work! - - - CLI now supports writing to file:// URL for 'upload-examples' command - - - GZipped content is now supported for client-to-server uploads (create, update, transaction, etc.). - The server will not automatically detect compressed incoming content and decompress it (this can be - disabled using a RestfulServer configuration setting). A new client interceptor has been added - which compresses outgoing content from the client. - - - JPA server transaction attempted to validate resources twice each, - with one of these times being before anything had been committed to the - database. This meant that if a transaction contained both a Questionnaire - and a QuestionnaireResponse, it would fail because the QuestionnaireResponse - validator wouldn't be able to find the questionnaire. This is now corrected. - - - Add a new method to the generic/fluent client for searching: - .count(int)
        ]]> - This replaces the existing ".limitTo(int)" method which has - now been deprocated because it was badly named and undocumented. -
        - - Profile validator has been configured to allow extensions even if they - aren't explicitly declared in the profile. - - - Add a constraint that the Maven build will only run in JDK 8+. HAPI - remains committed to supporting JDK 6+ in the compiled library, but these - days it can only be built using JDK 8. Thanks to joelsch for the PR! - - - When serializing a value[x] field, if the value type was a profiled type (e.g. markdown is a - profile of string) HAPI 1.3 would use the base type in the element name, e.g. - valueString instead of valueMarkdown. After discussion with Grahame, this appears to - be incorrect behaviour so it has been fixed. - - - Support target parameter type in _include / _revinclude values, e.g. - _include=Patient:careProvider:Organization. Thanks to Joe Portner - for reporting! - - - Use ResponseHighlighterInterceptor in the hapi-fhir-jpaserver-example - project to provide nice syntax highlighting. Thanks to Rob Hausam for - noting that this wasn't there. - - - Introduce custom @CoverageIgnore annotation to hapi-fhir-base in order to - remove dependency on cobertura during build and in runtime. - - - Server-generated conformance statements incorrectly used /Profile/ instead - of /StructureDefinition/ in URL links to structures. - - - JsonParser has been changed so that when serializing numbers it will use - plain format (0.001) instead of scientific format (1e-3). The latter is - valid JSON, and the parser will still correctly parse either format (all - clients should be prepared to) but this change makes serialized - resources appear more consistent between XML and JSON. As a result of this - change, trailing zeros will now be preserved when serializing as well. - - - Add DSTU3 example to hapi-fhir-jpaserver-example. Thanks to Karl - Davis for the Pull Request! - - - RestfulServer#setUseBrowserFriendlyContentTypes has been deprecated and its - functionality removed. The intention of this feature was that if it - detected a request coming in from a browser, it would serve up JSON/XML - using content types that caused the browsers to pretty print. But - each browser has different rules for when to pretty print, and - after we wrote that feature both Chrome and FF changed their rules to break it anyhow. - ResponseHighlightingInterceptor provides a better implementation of - this functionality and should be used instead. - - - Narrative generator framework has removed the - ability to generate resource titles. This - functionality was only useful for DSTU1 - implementations and wasn't compatible - with coming changes to that API. - - - Remove dependency on Servlet-API 3.0+ by using methods available in 2.5 where possible. - Note that we continue to use Servlet-API 3.0+ features in some parts of the JPA API, so - running in an old serlvet container should be tested well before use. Thanks to Bill Denton - for reporting! - - - Add new methods to RestfulClientFactory allowing you to configure the size of the - client pool used by Apache HttpClient. Thanks to Matt Blanchette for the pull - request! - - - Add support for new modifier types on Token search params in Server and - annotation client. - - - Server conformance statement should include search parameter chains if the - chains are explicitly defined via @Search(whitelist={....}). Thanks to lcamilo15 - for reporting! - - - Remove afterPropertiesSet() call in Java config for JPA - server's EntityManagerFactory. This doesn't need to be called - manually, the the manual call led to a warning about - the EntityManager being created twice. - - - Allow server to correctly figure out it's own address even if the container provides - a Servlet Context Path which does not include the root. Thanks to Petro Mykhaylyshyn - for the pull request! - -
        - - - Bump the version of a few dependencies to the - latest versions (dependent HAPI modules listed in brackets): - -
      • Commons-lang3 (Core): 3.3.2 -> 3.4
      • -
      • Logback (Core): 1.1.2 -> 1.1.3
      • -
      • SLF4j (Core): 1.7.102 -> 1.7.12
      • -
      • Springframework (JPA, Web Tester): 4.1.5 -> 4.2.2
      • -
      • Hibernate (JPA, Web Tester): 4.2.17 -> 5."
      • -
      • Hibernate Validator (JPA, Web Tester): 5.2.1 -> 5.2.2
      • -
      • Derby (JPA, CLI, Public Server): 10.11.1.1 -> 10.12.1.1
      • -
      • Jetty (JPA, CLI, Public Server): 9.2.6.v20141205 -> 9.3.4.v20151007
      • - - ]]> -
        - - JPA and Tester Overlay now use Spring Java config files instead - of the older XML config files. All example projects have been updated. - - - JPA server removes duplicate resource index entries before storing them - (e.g. if a patient has the same name twice, only one index entry is created - for that name) - - - JPA server did not correctly index search parameters of type "reference" where the - path had multiple entries (i.e. "Resource.path1 | Resource.path2") - - - JPA server _history operations (server, type, instance) not correctly set the - Bundle.entry.request.method to POST or PUT for create and updates of the resource. - - - Support AND/OR on _id search parameter in JPA - - - Constructor for DateRanfeParam which dates in two DateParam instances was ignoring - comparators on the DateParam. - - - In JSON parsing, finding an object where an array was expected led to an unhelpful - error message. Thanks to Avinash Shanbhag for reporting! - - - JPA server gave an unhelpful error message if $meta-add or $meta-delete were called - with no meta elements in the input Parameters - - - Narrative generator did not include OperationOutcome.issue.diagnostics in the - generated narrative. - - - Clients (generic and annotation) did not populate the Accept header on outgoing - requests. This is now populated to indicate that the client supports both XML and - JSON unless the user has explicitly requested one or the other (in which case the - appropriate type only will be send in the accept header). Thanks to - Avinash Shanbhag for reporting! - - - QuestionnaireResponse validator now allows responses to questions of - type OPENCHOICE to be of type 'string' - - - JPA server should reject resources with a reference that points to an incorrectly typed - resource (e.g. points to Patient/123 but resource 123 is actually an Observation) or points - to a resource that is not valid in the location it is found in (e.g. points to Patient/123 but - the field supposed to reference an Organization). Thanks to Bill de Beaubien for reporting! - - - In server, if a client request is received and it has an Accept header indicating - that it supports both XML and JSON with equal weight, the server's default is used instead of the first - entry in the list. - - - JPA server now supports searching with sort by token, quantity, - number, Uri, and _lastUpdated (previously only string, date, and _id - were supported) - - - Fix issue in JPA where a search with a _lastUpdated filter which matches no results - would crash if the search also had a _sort - - - Fix several cases where invalid requests would cause an HTTP 500 instead of - a more appropriate 400/404 in the JPA server (vread on invalid version, - delete with no ID, etc.) - - - Fix narrative generation for DSTU2 Medication resource - - - Profile validator now works for valuesets which use - v2 tables - - - JPA server Patient/[id]/$everything operation now supports - _lastUpdated filtering and _sort'ing of results. - - - Fix parser issue where profiled choice element datatypes (e.g. value[x] where one allowable - type is Duration, which is a profile of Quantity) get incorrectly encoded using the - profiled datatype name instead of the base datatype name as required by the FHIR - spec. Thanks to Nehashri Puttu Lokesh for reporting! - - - Some generated Enum types in DSTU2 HAPI structures - did not have latest valueset definitions applied. Thanks - to Bill de Beaubien for reporting! - - - JPA server can now successfully search for tokens pointing at code values - (values with no explicit system but an implied one, such as Patient.gender) - even if the system is supplied in the query. - - - Correct issues with Android library. Thanks to - Thomas Andersen for the submission! - - - JPA server incorrectly rejected match URLs - if they did not contain a question mark. Thanks - to Bill de Beaubien for reporting! - - - Remove invalid entries in OSGi Manifest. Thanks - to Alexander Kley for the fix! - - - JPA server now supports $everything on Patient and Encounter types (patient and encounter instance was - already supported) - - - Generic client operation invocations now - have an additional inline method for generating the input - Parameters using chained method calls instead - of by passing a Parameters resource in - - - Parsing an XML resource where the XHTML - namespace was declared before the beginning - of the narrative section caused an invalid - re-encoding when encoding to JSON. - - - Conditional deletes in JPA did not correctly - process if the condition had a chain or a - qualifier, e.g. "Patient?organization.name" or - "Patient.identifier:missing" - - - Generic/fluent client search can now be - performed using a complete URL supplied - by user code. Thanks to Simone Heckmann - pointing out that this was needed! - - - Refactor JPA $everything operations so that - they perform better - - - Server operation methods can now declare the - ID optional, via - @IdParam(optional=true) - meaning that the same operation can also be invoked - at the type level. - - - Make JPA search queries with _lastUpdated parameter a bit more efficient - - - Clean up Android project to make it more lightweight and remove a - number of unneeded dependencies. Thanks to Thomas Andersen - for the pull request! - - - Fix a crash when encoding a Binary resource in JSON encoding - if the resource has no content-type - - - JPA server now supports read/history/search in transaction entries - by calling the actual implementing method in the server (previously - the call was simulated, which meant that many features did not work) - - - ResourceReferenceDt#loadResource(IRestfulClient) did not - use the client's read functionality, so it did not - handle JSON responses or use interceptors. Thanks to - JT for reporting! - - - JPA server maximumn length for a URI search parameter has been reduced from - 256 to 255 in order to accomodate MySQL's indexing requirements - - - Server failed to respond correctly to compartment search operations - if the same provider also contained a read operation. Thanks to GitHub user - @am202 for reporting! - - - Fix issue in testpage-overlay's new Java configuration where only the first - configured server actually gets used. - - - Introduce - IJpaServerInterceptor - interceptors for JPA server which can be used for more fine grained operations. - - - Parser (XML and JSON) shouldn't encode an ID tag in resources - which are part of a bundle when the resource has a UUID/OID - ID. - - - Add ability for a server REST resource provider @Search method - to declare that it should allow even parameters it doesn't - understand. - - - Correctly set the Bundle.type value on all pages of a search result in - the server, and correcltly set the same value in JPA server $everything - results. - - - JPA $everything operations now support new parameters _content - and _text, which work the same way as the same parameters on a - search. This is experimental, since it is not a part of the core - FHIR specification. - - - Process "Accept: text/xml" and "Accept: text/json" headers was - wanting the equivalent FHIR encoding styles. These are not - correct, but the intention is clear so we will honour them - just to be helpful. - - - Generated Enum types for some ValueSets did not include all - codes (specifically, ValueSets which defined concepts containing - child concepts did not result in Enum values for the child concepts) - - - In the JPA server, order of transaction processing should be - DELETE, POST, PUT, GET, and the order should not matter - within entries with the same verb. Thanks to Bill de Beaubien - for reporting! - - - Add the ability to wire JPA conformance providers - using Spring (basically, add default constructors - and setters to the conformance providers). Thanks - to C. Mike Bylund for the pull request! - -
        - - - JPA server now validates QuestionnaireAnswers for conformance to their respective Questionnaire - if one is declared. - - - SyntaxHighlightingInterceptor now also highlights OperationOutcome responses for errors/exceptions. - - - Model classes do not use BoundCodeableConcept for example bindings that do not - actually point to any codes (e.g. Observation.interpretation). Thanks - to GitHub user @steve1medix for reporting! - - - Server now exports operations as separate resources instead of as contained resources - within Conformance - - - Add new operation $get-resource-counts which will replace the resource - count extensions exported in the Conformance statement by the JPA - server. - - - JPA server sorting often returned unexpected orders when multiple - indexes of the same type were found on the same resource (e.g. multiple string indexed fields). Thanks to - Travis Cummings for reporting! - - - Add another method to IServerInterceptor which converts an exception generated on the server - into a BaseServerResponseException. This is useful so that servers using ResponseHighlighterInterceptor - will highlight exceptions even if they aren't created with an OperationOutcome. - - - XmlParser and JsonParser in DSTU2 mode should not encode empty - tags in resource. Thanks to Bill De Beaubien for reporting! - - - OperationDefinitions generated by server did not properly document - their return parameters or the type of their input parameters. - - - Operations in server generated conformance statement should only - appear once per name, since the name needs to be unique. - - - Resources and datatypes are now serializable. This is an - experimental feature which hasn't yet been extensively tested. Please test and give us your feedback! - - - Switch REST server to using HttpServletRequest#getContextPath() to get - the servlet's context path. This means that the server should behave more - predictably, and should work in servlet 2.4 environments. Thanks to - Ken Zeisset for the suggestion! - - - Vagrant environment now has an apt recipt to ensure that - package lists are up to date. Thanks to GitHub user - Brian S. Corbin (@corbinbs) for thr contribution! - - - JPA server and generic client now both support the _tag search parameter - - - Add support for BATCH mode to JPA server transaction operation - - - Server was not correctly unescaping URL parameter values with - a trailing comma or an escaped backslash. Thanks to GitHub user - @SherryH for all of her help in diagnosing this issue! - - - Avoid crash when parsing if an invalid child element is found in - a resource reference. - - - Create new android specialty libraries for DSTU1 and DSTU2 - - - Throwing a server exception (e.g. AuthenticationException) in a server interceptor's - incomingRequestPreProcessed method resulted in the server returning an HTTP 500 instead - of the appropriate error code for the exception being thrown. Thanks to Nagesh Bashyam - for reporting! - - - Fix issue in JSON parser where invalid contained resources (missing - a resourceType element) fail to parse with a confusing NullPointerException. - Thanks to GitHub user @hugosoares for reporting! - - - JPA server now implements the $validate-code operation - - - HAPI-FHIR now has support for _summary and _elements parameters, in server, client, - and JPA server. - - - _revinclude results from JPA server should have a Bundle.entry.search.mode of - "include" and not "match". Thanks to Josh Mandel for reporting! - - - Resource references using resource instance objects instead of resource IDs - will correctly qualify the IDs with the resource type if they aren't already qualified - - - Testpage Overlay project now properly allows a custom client - factory to be used (e.g. for custom authentication, etc.) Thanks - to Chin Huang (@pukkaone) for the pull request! - - - JPA server should reject IDs containing invalid characters (e.g. "abc:123") - but should allow client assigned IDs that contain text but do not start with - text. Thanks to Josh Mandel for reporting! - - - :text modifier on server and JPA server did not work correctly. Thanks to - Josh Mandel for reporting! - - - Fix issue in client where parameter values containing a comma were - sometimes double escaped. - - - _include parameters now support the new _include:recurse=FOO]]> - syntax that has been introduced in DSTU2 in the Client, Server, and JPA Server modules. - Non-recursive behaviour is now the default (previously it was recursive) and :recurse - needs to be explicitly stated in order to support recursion. - - - New operations added to JPA server to force re-indexing of all - resources (really only useful after indexes change or bugs are - fixed) - - - JPA server did not correctly index search parameters - of type "URI". Thanks to David Hay for reporting! Note that if you are using the JPA server, this change - means that - there are two new tables added to the database schema. Updating existing resources in the database may fail - unless you - set default values for the resource - table by issuing a SQL command similar to the following (false may be 0 or something else, depending on the - database platform in use) - update hfj_resource set sp_coords_present = false;
        - update hfj_resource set sp_uri_present = false;
        ]]> -
        - - FIx issue in JPA server where profile declarations, tags, and - security labels were not always properly removed by an update that - was trying to remove them. Also don't store duplicates. - - - Instance $meta operations on JPA server did not previously return the - resource version and lastUpdated time - - - Server responses populate Bundle.entry.fullUrl if possible. Thanks - to Bill de Beaubien for reporting! - - - XML parser failed to initialize in environments where a very old Woodstox - library is in use (earlier than 4.0). Thanks to Bill de Beaubien for - reporting! - - - Invalid/unexpected attributes found when parsing composite elements - should be logged or reported to the parser error handler - - - JPA server can now store Conformance resources, per a request - from David Hay - - - ResponseHighlightingInterceptor now skips handling responses if it - finds a URL parameter of _raw=true]]> (in other - words, if this parameter is found, the response won't be returned as - HTML even if the request is detected as coming from a browser. - - - RestfulServer now supports dynamically adding and removing resource providers - at runtime. Thanks to Bill Denton for adding this. - - - JPA server now correctly suppresses contents of deleted resources - in history - - - JPA server returned deleted resources in search results when using the _tag, _id, _profile, or _security - search parameters - - - Fix issue with build on Windows. Thanks to Bryce van Dyk for the pull request! - - - JPA server now supports $validate operation completely, including delete mode - and profile validation using the RI InstanceValidator - -
        - - - Add support for reference implementation structures. - - - Parsers did not encode the resource meta element if the resource - had tags but no other meta elements. Thanks to Bill de Beaubien and - Claude Nanjo for finding this. - - - Correct performance issue with :missing=true search requests where the parameter is a resource link. Thanks - to wanghaisheng for all his help in testing this. - - - The self link in the Bundle returned by searches on the server does not respect the - server's address strategy (which resulted in an internal IP being shown on fhirtest.uhn.ca) - - - Introduce ResponseHighlighterInterceptor, which provides syntax highlighting on RESTful server responses - if the server detects that the request is coming from a browser. This interceptor has been added - to fhirtest.uhn.ca responses. - - - Performing a create operation in a client used an incorrect URL if the - resource had an ID set. ID should be ignored for creates. Thanks to - Peter Girard for reporting! - - - Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to - Claude Nanjo for the - suggestion! - - - Add a new parser validation mechanism (see the - validation page]]> for info) which can be - used to validate resources as they are being parsed, and optionally fail if invalid/unexpected - elements are found in resource bodies during parsing. - - - IParser#parseResource(Class, String) method, which is used to parse a resource into the given - structure will now throw a DataFormatException if the structure is for the wrong type of - resource for the one actually found in the input String (or Reader). For example, if a Patient - resource is being parsed into Organization.class this will now cause an error. Previously, - the XML parser would ignore the type and the JSON parser would fail. This also caused - operations to not parse correctly if they returned a resource type other than - parameters with JSON encoding (e.g. the $everything operation on UHN's test server). - Thanks to Avinash Shanbhag for reporting! - - - Web tester UI now supports _revinclude - - - Support link elements in Bundle.entry when parsing in DSTU2 mode - using the old (non-resource) Bundle class. Thanks to GitHub user - @joedai for reporting! - - - LoggingInterceptor for server now supports logging DSTU2 extended operations by name - - - Woodstox XML parser has a default setting to limit the maximum - length of an attribute to 512kb. This caused issues handling - large attachments, so this setting has been increased to 100Mb. - Thanks to Nikos Kyriakoulakos for reporting! - - - Some HTML entities were not correctly converted during parsing. Thanks to - Nick Kitto for reporting! - - - In the JPA Server: - Transactions creating resources with temporary/placeholder resource IDs - and other resources with references to those placeholder IDs previously - did not work if the reference did not contain the resource type - (e.g. Patient/urn:oid:0.1.2.3 instead of urn:oid:0.1.2.3). The - latter is actually the correct way of specifying a reference to a - placeholder, but the former was the only way that worked. Both forms - now work, in order to be lenient. Thanks to Bill De Beaubien for - reporting! - - - When parsing Bundles, if Bundle.entry.base is set to "cid:" (for DSTU1) - or "urn:uuid:" / "urn:oid:" (for DSTU2) this is now correctly passed as - the base in resource.getId(). Conversely, when - encoding bundles, if a resource ID has a base defined, - and Bundle.entry.base is empty, it will now be - automatically set by the parser. - - - Add fluent client method for validate operation, and support the - new DSTU2 style extended operation for $validate if the client is - in DSTU2 mode. Thanks to Eric from the FHIR Skype Implementers chat for - reporting. - - - Server now supports complete Accept header content negotiation, including - q values specifying order of preference. Previously the q value was ignored. - - - Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to - Kevin Paschke for pointing out that this wasn't working! - - - Questionnaire.title now gets correctly indexed in JPA server (it has no path, so it is a special case) - - - JPA server now supports ifNoneMatch in GET within a transaction request. - - - DateRangeParam now supports null values in the constructor for lower or upper bounds (but - still not both) - - - Generic/fluent client and JPA server now both support _lastUpdated search parameter - which was added in DSTU2 - - - JPA server now supports sorting on reference parameters. Thanks to - Vishal Kachroo for reporting that this wasn't working! - - - Prevent Last-Updated header in responses coming back to the client from - overwriting the 'lastUpdated' value in the meta element in DSTU2 - resources. This is important because 'lastUpdated' can have more - precision than the equivalent header, but the client previously - gave the header priority. - - - JPA server supports _count parameter in transaction containing search URL (nested search) - - - DSTU2 servers now indicate support for conditional create/update/delete in their - conformance statement. - - - Support for the Prefer header has been added to the server, client, and - JPA modules. - - - JPA server failed to search for deep chained parameters across multiple references, - e.g. "Location.partof.partof.organization". Thanks to Ismael Sarmento Jr for - reporting! - - - Prevent crash when encoding resources with contained resources - if the contained resources contained a circular reference to each other - - - Add $meta, $meta-add, and $meta-delete operations to generic client - - - - - Bump the version of a few dependencies to the - latest versions: - -
      • Phloc-commons (for schematron validation) 4.3.5 -> 4.3.6
      • -
      • Apache HttpClient 4.3.6 -> 4.4
      • -
      • Woodstox 4.4.0 -> 4.4.1
      • -
      • SLF4j 1.7.9 -> 1.7.10
      • -
      • Spring (used in hapi-fhir-jpaserver-base module) 4.1.3.RELEASE -> 4.1.5.RELEASE
      • - - ]]> -
        - - Add support for "profile" and "tag" elements in the resource Meta block - when parsing DSTU2 structures. - - - When a user manually creates the list of contained resources in a resource, - the encoder fails to encode any resources that don't have a '#' at the - start of their ID. This is unintuitive, so we now assume that '123' means '#123'. - Thanks to myungchoi for reporting and providing a test case! - - - Add methods for setting the default encoding (XML/JSON) and - oretty print behaviour in the Fluent Client. Thanks to Stackoverflow - user ewall for the idea. - - - JPA Server did not mark a resource as "no longer deleted" if it - was updated after being deleted. Thanks to Elliott Lavy and Lloyd - McKenzie for reporting! - - - Fix regression in 0.9 - Server responds with an HTTP 500 and a NullPointerException instead of an HTTP 400 - and a useful error message if the client requests an unknown resource type - - - Add support for - _revinclude]]> - parameter in client, server, and JPA. - - - Include constants on resources (such as - Observation.INCLUDE_VALUE_STRING]]>) - have been switched in the DSTU2 structures to use - the new syntax required in DSTU2: [resource name]:[search param NAME] - insead of the DSTU1 style [resource name].[search param PATH] - - - When encoding resources, the parser will now convert any resource - references to versionless references automatically (i.e. it will - omit the version part automatically if one is present in the reference) - since references between resources must be versionless. Additionally, - references in server responses will omit the server base URL part of the - reference if the base matches the base for the server giving - the response. - - - Narrative generator incorrectly sets the Resource.text.status to 'generated' even if the - given resource type does not have a template (and therefore no narrative is actually generated). - Thanks to Bill de Beaubien for reporting! - - - Searching in JPA server with no search parameter returns deleted resources when it should exclude them. - - - Remove Eclipse and IntelliJ artifacts (.project, *.iml, etc) from version control. Thanks - to Doug Martin for the suggestion! - - - REST server methods may now have a parameter of - type NarrativeModeEnum which will be populated with - the value of the _narrative URL parameter - if one was supplied. Annotation client methods - may also include a parameter of this type, and it - will be used to populate this parameter on the request - URL if it is not null. Thanks to Neal Acharya for the - idea! - - - Android JAR now includes servlet-API classes, as the project will not - work without them. Thanks - - - Requested _include values are preserved across paging links when the - server returns multiple pages. Thanks to Bill de Beaubien for - reporting! - - - Add new server address strategy "ApacheProxyAddressStrategy" which uses - headers "x-forwarded-host" and "x-forwarded-proto" to determine the - server's address. This is useful if you are deploying a HAPI FHIR - server behind an Apache proxy (e.g. for load balancing or other reasons). - Thanks to Bill de Beaubien for contributing! - - - Resource references between separate resources found in a single - bundle did not get populated with the actual resource when parsing a - DSTU2 style bundle. Thanks to Nick Peterson for reporting and figuring - out why none of our unit tests were actually catching the problem! - - - JSON encoder did not encode contained resources when encoding - a DSTU2 style bundle. Thanks to Mohammad Jafari and baopingle - for all of their help in tracking this issue down and developing - useful unit tests to demonstrate it. - - - Client now supports invoking transcation using a DSTU2-style - Bundle resource as the input. - - - JPA Server $everything operation could sometimes include a duplicate copy of - the main focus resource if it was referred to in a deep chain. Thanks - to David Hay for reporting! - - - JPA Server $everything operation now allows a _count parameter - - - JPA server failed to index resources containing ContactPointDt elements with - populated values (e.g. Patient.telecom). Thanks to Mohammad Jafari for reporting! - - - Add a new configuration method on the parsers, - setStripVersionsFromReferences(boolean)]]> which - configures the parser to preserve versions in resource reference links when - encoding. By default, these are removed. - - - Terser's IModelVisitor now supplies to the path to the element. This is - an API change, but I don't think there are many users of the IModelVisitor yet. - Please let us know if this is a big hardship and we can find an alternate way - of making this change. - - - Prevent server from returning a Content-Location header for search - response when using the DSTU2 bundle format - - - JPA server (uhnfhirtest.uhn.ca) sometimes included an empty - "text" element in Bundles being returned. - - - Add a framework for the Web Tester UI to allow its internal FHIR client to - be configured (e.g. to add an authorization interceptor so that it adds - credentials to client requests it makes). Thanks to Harsha Kumara for - the suggestion! - - - Fix regression in early 1.0 builds where resource type sometimes does not get - populated in a resource ID when the resource is parsed. Thanks to - Nick Peterson for reporting, and for providing a test case! - - - Allow fluent/generic client users to execute a transaction using a raw string (containing a bundle resource) - as input instead of a Bundle resource class instance. - - - Disable date validation in the web tester UI, so that it is possible to - enter partial dates, or dates without times, or even test out invalid date - options. - - - Make BaseElement#getUndeclaredExtensions() and BaseElement#getUndeclaredExtensions() return - a mutable list so that it is possible to delete extensions from a resource instance. - - - Server conformance statement check in clients (this is the check - where the first time a given FhirContext is used to access a given server - base URL, it will first check the server's Conformance statement to ensure - that it supports the correct version of FHIR) now uses any - registered client interceptors. In addition, IGenericClient now has a method - "forceConformanceCheck()" which manually triggers this check. Thanks to - Doug Martin for reporting and suggesting! - - - Rename the Spring Bean definition for the JPA server EntityManager from - "myEntityManagerFactory" to just "entityManagerFactory" as this is the - default bean name expected in other parts of the Spring framework. - Thanks to Mohammad Jafari for the suggestion! - - - Improve error message when a user tries to perform a create/update with an invalid - or missing Content-Type header. Thanks to wanghaisheng for reporting! (This was - actually a three part bug, so the following two fixes also reference this - bug number) - - - Add support for :missing qualifier in generic/fluent client. - - - Add support for :missing qualifier in JPA server. - - - Add a new configuration method on the parsers, - setStripVersionsFromReferences(boolean)]]> which - configures the parser to preserve versions in resource reference links when - encoding. By default, these are removed. - - - Add an exception for RESTful clients/servers to represent the - HTTP 403 Forbidden status code. Thanks to Joel Costigliola for - the patch! - - - Transaction server operations incorrectly used the "Accept" header instead of the "Content-Type" header to - determine the - POST request encoding. Thanks to Rene Spronk for providing a test case! - -
        - - - Support for DSTU2 features introduced: New resource definitions, Bundle resource, - encoding changes (ID in resource bodt, meta tag) - - - Fix an issue encoding extensions on primitive types in JSON. Previously the "_value" object - would be an array even if the field it was extending was not repeatable. This is not correct - according to the specification, nor can HAPI's parser parse this correctly. The encoder - has been corrected, and the parser has been adjusted to be able to handle resources with - extensions encoded in this way. Thanks to Mohammad Jafari for reporting! - - - Library now checks if custom resource types can be instantiated on startup - (e.g. because they don't have a no-argument constructor) in order to - avoid failing later - - - Bump a few dependency JARs to the latest versions in Maven POM: - -
      • SLF4j (in base module) - Bumped to 1.7.9
      • -
      • Apache HTTPClient (in base module) - Bumped to 4.3.6
      • -
      • Hibernate (in JPA module) - Bumped to 4.3.7
      • - - ]]> -
        - - IdDt failed to recognize local identifiers containing fragments that look like - real identifiers as being local identifiers even though they started with '#'. - For example, a local resource reference of "#aa/_history/aa" would be incorrectly - parsed as a non-local reference. - Thanks to Mohammad Jafari for reporting! - - - Last-Modified]]> - header in server was incorrectly using FHIR date format instead - of RFC-1123 format. - - - Server create and update methods failed with an IllegalArgumentException if - the method type was a custom resource definition type (instead of a built-in - HAPI type). Thanks to Neal Acharya for the analysis. - - - JPA server module now supports - _include]]> - value of - *]]>. Thanks to Bill de Beaubien for reporting! - - - IdDt method - - returned String (unlike all of the other "withFoo" methods on that class), - and did not work correctly if the IdDt already had a server base. This - has been corrected. Note that the return type for this method has been - changed, so code may need to be updated. - - - In previous versions of HAPI, the XML parser encoded multiple contained - resources in a single - <contained></contained>]]> - tag, even though the FHIR specification rerquires a separate - <contained></contained>]]> - tag for each resource. This has been corrected. Note that the parser will - correctly parse either form (this has always been the case) so this - change should not cause any breakage in HAPI based trading partners, but - may cause issues if other applications have been coded to depend on the - incorrect behaviour. Thanks to Mochaholic for reporting! - - - Custom/user defined resource definitions which contained more than one - child with no order defined failed to initialize properly. Thanks to - Andy Huang for reporting and figuring out where the - problem was! - - - RESTful Client now queries the server (only once per server base URL) to ensure that - the given server corresponds to the correct version of the FHIR specification, as - defined by the FhirContext. This behaviour can be disabled by setting the - appropriate configuration on the - RestfulClientConfig. Thanks to Grahame Grieve for the suggestion! - - - JPA module now supports deleting resource via transaction - - - DateClientParam#second() incorrectly used DAY precision instead - of SECOND precision. Thanks to Tom Wilson for the pull request! - - - Fix issue where HAPI failed to initialize correctly if Woodstox library was not on the classpath, even - if StAX API was configured to use a different provider. Thanks to - James Butler for reporting and figuring out where the issue was! - - - Calling BaseDateTimeDt#setValue(Date, TemporalPrecisionEnum) did not always actually respect - the given precision when the value was encoded. Thanks to jacksonjesse for - reporting! - - - Encoders (both XML and JSON) will no longer encode contained resources if they are - not referenced anywhere in the resource via a local reference. This is just a convenience - for users who have parsed a resource with contained resources and want to remove some - before re-encoding. Thanks to Alexander Kley for reporting! - - - Add support for DSTU2 style security labels in the parser and encoder. Thanks to - Mohammad Jafari for the contribution! - - - Server requests for Binary resources where the client has explicitly requested XML or JSON responses - (either with a _format]]> URL parameter, or an Accept]]> request - header) - will be responded to using the Binary FHIR resource type instead of as Binary blobs. This is - in accordance with the recommended behaviour in the FHIR specification. - - - Add new properties to RestfulServer: "DefaultResponseEncoding", which allows - users to configure a default encoding (XML/JSON) to use if none is specified in the - client request. Currently defaults to XML. Also "DefaultPrettyPrint", which specifies - whether to pretty print responses by default. Both properties can be overridden - on individual requets using the appropriate Accept header or request URL parameters. - - - Add support for quantity search params in FHIR tester UI - - - Add support for FHIR "extended operations" as defined in the FHIR DSTU2 - specification, for the Generic Client, Annotation Client, and - Server. - - - Observation.applies[x] and other similar search fields with multiple allowable - value types were not being correctly indexed in the JPA server. - - - DateClientParam.before() incorrectly placed "<=" instead of - "<" in the request URL. Thanks to Ryan for reporting! - - - Server now only automatically adds _include resources which are provided - as references if the client request actually requested that specific include. - See RestfulServer - - - User defined resource types which contain extensions that use a bound code type - (e.g. an BoundCodeDt with a custom Enum) failed to parse correctly. Thanks - to baopingle for reporting and providing a test case! - - - Sorting is now supported in the Web Testing UI (previously a button existed for sorting, but it didn't do - anything) - - - Server will no longer include stack traces in the OperationOutcome returned to the client - when an exception is thrown. A new interceptor called ExceptionHandlingInterceptor has been - created which adds this functionality back if it is needed (e.g. for DEV setups). See the - server interceptor documentation for more information. Thanks to Andy Huang for the suggestion! - -
        - - - API CHANGE:]]> The "FHIR structures" for DSTU1 (the classes which model the - resources and composite datatypes) have been moved out of the core JAR into their - own JAR, in order to allow support for DEV resources, and DSTU2 resources when thast - version is finalized. See the - DSTU2 page]]> - for more information. - - - Deprecated API Removal: The following classes (which were deprocated previously) - have now been removed: -
          -
        • ISecurityManager: If you are using this class, the same functionality - is available through the more general purpose - server interceptor - capabilities.
        • -
        • CodingListParam: This class was made redundant by the - TokenOrListParam - class, which can be used in its place.
        • -
        - ]]> -
        - - API Change: The IResource#getResourceMetadata() method has been changed - from returning - Map<ResourceMetadataKeyEnum<?>, Object> - to returning a new type called - ResourceMetadataMap. This new type implements - Map<ResourceMetadataKeyEnum<?>, Object> - itself, so this change should not break existing code, but may - require a clean build in order to run correctly. - ]]> - - - Profile generation on the server was not working due to IdDt being - incorrectly used. Thanks to Bill de Beaubien for the pull request! - - - Profiles did not generate correctly if a resource definition class had a - defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request! - - - Remove unnecessary IOException from narrative generator API. Thanks to - Petro Mykhailysyn for the pull request! - - - Introduced a new - @ProvidesResources]]> annotation which can be added to - resource provider and servers to allow them to declare additional resource - classes they are able to serve. This is useful if you have a server which can - serve up multiple classes for the same resource type (e.g. a server that sometimes - returns a default Patient, but sometimes uses a custom subclass). - Thanks to Bill de Beaubien for the pull request! - - - Introduced a new - @Destroy]]> annotation which can be added to - a resource provider method. This method will be called by the server when it - is being closed/destroyed (e.g. when the application is being undeployed, the - container is being shut down, etc.) - Thanks to Bill de Beaubien for the pull request! - - - Add a new method to the server interceptor - framework which allows interceptors to be notified of any exceptions and - runtime errors within server methods. Interceptors may optionally also - override the default error handling behaviour of the RestfulServer. - - - Add constants to BaseResource for the "_id" search parameter which all resources - should support. - - - DateRangeParam parameters on the server now return correct - getLowerBoundAsInstant()]]> - and - getUpperBoundAsInstant()]]> - values if a single unqualified value is passed in. For example, if - a query containing - &birthdate=2012-10-01]]> - is received, previously these two methods would both return the same - value, but with this fix - getUpperBoundAsInstant()]]> - now returns the instant at 23:59:59.9999. - - - Resource fields with a type of "*" (or Any) sometimes failed to parse if a - value type of "code" was used. Thanks to Bill de Beaubien for reporting! - - - Remove dependency on JAXB libraries, which were used to parse and encode - dates and times (even in the JSON parser). JAXB is built in to most JDKs - but the version bundled with IBM's JDK is flaky and resulted in a number - of problems when deploying to Websphere. - - - Primitive datatypes now preserve their original string value when parsing resources, - as well as containing the "parsed value". For instance, a DecimalDt field value of - 1.0000]]> will be parsed into the corresponding - decimal value, but will also retain the original value with the corresponding - level of precision. This allows vadliator rules to be applied to - original values as received "over the wire", such as well formatted but - invalid dates, e.g. "2001-15-01". Thanks to Joe Athman for reporting and - helping to come up with a fix! - - - When using Generic Client, if performing a - or operation using a String as the resource body, - the client will auto-detect the FHIR encoding style and send an appropriate - header. - - - JPA module (and public HAPI-FHIR test server) were unable to process resource types - where at least one search parameter has no path specified. These now correctly save - (although the server does not yet process these params, and it should). Thanks to - GitHub user shvoidlee for reporting and help with analysis! - - - Generic/Fluent Client "create" and "update" method requests were not setting a content type header - - - DateDt left precision value as in the constructor - . - - - RESTful server now doesn't overwrite resource IDs if they are absolute. In other words, if - a server's Resource Provider returns a resource with ID "Patient/123" it will be translated to - "[base url]/Patient/123" but if the RP returns ID "http://foo/Patient/123" the ID will be - returned exactly as is. Thanks to Bill de Beaubien for the suggestion! - - - JPA module Transaction operation was not correctly replacing logical IDs - beginning with "cid:" with server assigned IDs, as required by the - specification. - - - did not visit or find children in contained resources when - searching a resource. This caused server implementations to not always return contained - resources when they are included with a resource being returned. - - - Add a method which returns the name of the - resource in question (e.g. "Patient", or "Observation"). This is intended as a - convenience to users. - - - Do not strip version from resource references in resources returned - from server search methods. Thanks to Bill de Beaubien for reporting! - - - Correct an issue with the validator where changes to the underlying - OperationOutcome produced by a validation cycle cause the validation - results to be incorrect. - - - Client interceptors registered to an interface based client instance - were applied to other client instances for the same client interface as well. (Issue - did not affect generic/fluent clients) - - - DateDt, DateTimeDt and types InstantDt types now do not throw an exception - if they are used to parse a value with the wrong level of precision for - the given type but do throw an exception if the wrong level of precision - is passed into their constructors.
        ]]> - This means that HAPI FHIR can now successfully parse resources from external - sources that have the wrong level of precision, but will generate a validation - error if the resource is validated. Thanks to Alexander Kley for the suggestion! -
        - - Encoding a Binary resource without a content type set should not result in a NullPointerException. Thanks - to Alexander Kley for reporting! - - - Server gives a more helpful error message if multiple IResourceProvider implementations - are provided for the same resource type. Thanks to wanghaisheng for the idea! - - - Bring DSTU1 resource definitions up to version 0.0.82-2929]]> - Bring DEV resource definitions up to 0.4.0-3775]]> - Thanks to crinacimpian for reporting! - - - JPA server did not correctly process _include requests if included - resources were present with a non-numeric identifier. Thanks to - Bill de Beaubien for reporting! - - - Client requests which include a resource/bundle body (e.g. create, - update, transaction) were not including a charset in the content type - header, leading to servers incorrectly assuming ISO-8859/1. Thanks to - shvoidlee for reporting! - - - Clean up the way that Profile resources are automatically exported - by the server for custom resource profile classes. See the - @ResourceDef]]> - JavaDoc for information on how this works. - - - Add convenience methods to TokenOrListParam to test whether any of a set of tokens match - the given requested list. - - - Add a protected method to RestfulServer which allows developers to - implement their own method for determining which part of the request - URL is the FHIR request path (useful if you are embedding the RestulServer inside - of another web framework). Thanks to Harsha Kumara for the pull request! - -
        - - - API CHANGE:]]> The TagList class previously implemented ArrayList semantics, - but this has been replaced with LinkedHashMap semantics. This means that the list of - tags will no longer accept duplicate tags, but that tag order will still be - preserved. Thanks to Bill de Beaubien for reporting! - - - Server was incorrectly including contained resources being returned as both contained resources, and as - top-level resources in the returned bundle for search operations. - Thanks to Bill de Beaubien for reporting! This also fixes Issue #20, thanks to - lephty for reporting! - - - Documentation fixes - - - Add a collection of new methods on the generic client which support the - read, - read, - and search - ]]> - operations using an absolute URL. This allows developers to perform these operations using - URLs they obtained from other sources (or external resource references within resources). In - addition, the existing read/vread operations will now access absolute URL references if - they are passed in. Thanks to Doug Martin of the Regenstrief Center for Biomedical Informatics - for contributing this implementation! - - - Server implementation was not correctly figuring out its own FHIR Base URL when deployed - on Amazon Web Service server. Thanks to Jeffrey Ting and Bill De Beaubien of - Systems Made Simple for their help in figuring out this issue! - - - XML Parser failed to encode fields with both a resource reference child and - a primitive type child. Thanks to Jeffrey Ting and Bill De Beaubien of - Systems Made Simple for their help in figuring out this issue! - - - HAPI now runs successfully on Servlet 2.5 containers (such as Tomcat 6). Thanks to - Bernard Gitaadji for reporting and diagnosing the issue! - - - Summary (in the bundle entry) is now encoded by the XML and JSON parsers if supplied. Thanks to David Hay of - Orion Health for reporting this! - - - Conformance profiles which are automatically generated by the server were missing a few mandatory elements, - which meant that the profile did not correctly validate. Thanks to Bill de Beaubien of Systems Made Simple - for reporting this! - - - XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those - characters - escaped properly in encoded messages. - - - Resources containing entities which are not valid in basic XML (e.g. &sect;) will have those - entities converted to their equivalent unicode characters when resources are encoded, since FHIR does - not allow extended entities in resource instances. - - - Add a new client interceptor which adds HTTP Authorization Bearer Tokens (for use with OAUTH2 servers) - to client requests. - - - Add phloc-commons dependency explicitly, which resolves an issue building HAPI from source on - some platforms. Thanks to Odysseas Pentakalos for the patch! - - - HAPI now logs a single line indicating the StAX implementation being used upon the - first time an XML parser is created. - - - Update methods on the server did not return a "content-location" header, but - only a "location" header. Both are required according to the FHIR specification. - Thanks to Bill de Beaubien of Systems Made Simple for reporting this! - - - Parser failed to correctly read contained Binary resources. Thanks to Alexander Kley for - the patch! - - - Calling encode multiple times on a resource with contained resources caused the contained - resources to be re-added (and the actual message to grow) with each encode pass. Thanks to - Alexander Kley for the test case! - - - JSON-encoded contained resources with the incorrect "_id" element (which should be "id", but some - incorrect examples exist on the FHIR specification) now parse correctly. In other words, HAPI - previously only accepted the correct "id" element, but now it also accepts the incorrect - "_id" element just to be more lenient. - - - Several unit tests failed on Windows (or any platform with non UTF-8 default encoding). This may - have also caused resource validation to fail occasionally on these platforms as well. - Thanks to Bill de Beaubien for reporting! - - - toString() method on TokenParam was incorrectly showing the system as the value. - Thanks to Bill de Beaubien for reporting! - - - Documentation on contained resources contained a typo and did not actually produce contained resources. - Thanks - to David Hay of Orion Health for reporting! - - - Add a - Vagrant]]> - based environment (basically a fully built, self contained development environment) for - trying out the HAPI server modules. Thanks to Preston Lee for the pull request, and for - offering to maintain this! - - - Change validation API so that it uses a return type instead of exceptions to communicate - validation failures. Thanks to Joe Athman for the pull request! - - - Add a client interceptor which adds an HTTP cookie to each client request. Thanks to - Petro Mykhailysyn for the pull request! - - - - - - Add server interceptor framework, and new interceptor for logging incoming - requests. - - - Add server validation framework for validating resources against the FHIR schemas and schematrons - - - Tester UI created double _format and _pretty param entries in searches. Thanks to Gered King of University - Health Network for reporting! - - - Create method was incorrectly returning an HTTP 204 on sucessful completion, but - should be returning an HTTP 200 per the FHIR specification. Thanks to wanghaisheng - for reporting! - - - FHIR Tester UI now correctly sends UTF-8 charset in responses so that message payloads containing - non US-ASCII characters will correctly display in the browser - - - JSON parser was incorrectly encoding extensions on composite elements outside the element itself - (as is done correctly for non-composite elements) instead of inside of them. Thanks to David Hay of - Orion for reporting this! - - - Contained/included resource instances received by a client are now automatically - added to any ResourceReferenceDt instancea in other resources which reference them. - - - Add documentation on how to use eBay CORS Filter to support Cross Origin Resource - Sharing (CORS) to server. CORS support that was built in to the server itself has - been removed, as it did not work correctly (and was reinventing a wheel that others - have done a great job inventing). Thanks to Peter Bernhardt of Relay Health for all the assistance - in testing this! - - - IResource interface did not expose the getLanguage/setLanguage methods from BaseResource, - so the resource language was difficult to access. - - - JSON Parser now gives a more friendly error message if it tries to parse JSON with invalid use - of single quotes - - - Transaction server method is now allowed to return an OperationOutcome in addition to the - incoming resources. The public test server now does this in order to return status information - about the transaction processing. - - - Update method in the server can now flag (via a field on the MethodOutcome object being returned) - that the result was actually a creation, and Create method can indicate that it was actually an - update. This has no effect other than to switch between the HTTP 200 and HTTP 201 status codes on the - response, but this may be useful in some circumstances. - - - Annotation client search methods with a specific resource type (e.g. List<Patient>]]> search()) - won't return any resources that aren't of the correct type that are received in a response - bundle (generally these are referenced resources, so they are populated in the reference fields instead). - Thanks to Tahura Chaudhry of University Health Network for the unit test! - - - Added narrative generator template for OperationOutcome resource - - - Date/time types did not correctly parse values in the format "yyyymmdd" (although the FHIR-defined format - is "yyyy-mm-dd" anyhow, and this is correctly handled). Thanks to Jeffrey Ting of Systems Made Simple - for reporting! - - - Server search method for an unnamed query gets called if the client requests a named query - with the same parameter list. Thanks to Neal Acharya of University Health Network for reporting! - - - Category header (for tags) is correctly read in client for "read" operation - - - Transaction method in server can now have parameter type Bundle instead of - List<IBaseResource>]]> - - - HAPI parsers now use field access to get/set values instead of method accessors and mutators. - This should give a small performance boost. - - - JSON parser encodes resource references incorrectly, using the name "resource" instead - of the name "reference" for the actual reference. Thanks to - Ricky Nguyen for reporting and tracking down the issue! - - - Rename NotImpementedException to NotImplementedException (to correct typo) - - - Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with - 4xx/5xx status) - - - Fix performance issue in date/time datatypes where pattern matchers were not static - - - Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, - but - previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting! - - - Resource of type "List" failed to parse from a bundle correctly. Thanks to David Hay of Orion Health - for reporting! - - - QuantityParam correctly encodes approximate (~) prefix to values - - - If a server defines a method with parameter "_id", incoming search requests for that method may - get delegated to the wrong method. Thanks to Neal Acharya for reporting! - - - SecurityEvent.Object structural element has been renamed to - SecurityEvent.ObjectElement to avoid conflicting names with the - java Object class. Thanks to Laurie Macdougall-Sookraj of UHN for - reporting! - - - Text/narrative blocks that were created with a non-empty - namespace prefix (e.g. <xhtml:div xmlns:xhtml="...">...</xhtml:div>) - failed to encode correctly (prefix was missing in encoded resource) - - - Resource references previously encoded their children (display and reference) - in the wrong order so references with both would fail schema validation. - - - SecurityEvent resource's enums now use friendly enum names instead of the unfriendly - numeric code values. Thanks to Laurie MacDougall-Sookraj of UHN for the - suggestion! - - - - - HAPI has a number of RESTful method parameter types that have similar but not identical - purposes and confusing names. A cleanup has been undertaken to clean this up. - This means that a number of existing classes - have been deprocated in favour of new naming schemes. -
        ]]> - All annotation-based clients and all server search method parameters are now named - (type)Param, for example: StringParam, TokenParam, etc. -
        ]]> - All generic/fluent client method parameters are now named - (type)ClientParam, for example: StringClientParam, TokenClientParam, etc. -
        ]]> - All renamed classes have been retained and deprocated, so this change should not cause any issues - for existing applications but those applications should be refactored to use the - new parameters when possible. -
        - - Allow server methods to return wildcard generic types (e.g. List<? extends IResource>) - - - Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as - "&identifier=system|codepart1\|codepart2" - - - Add support for OPTIONS verb (which returns the server conformance statement) - - - Add support for CORS headers in server - - - Bump SLF4j dependency to latest version (1.7.7) - - - Add interceptor framework for clients (annotation based and generic), and add interceptors - for configurable logging, capturing requests and responses, and HTTP basic auth. - - - Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" - instead - of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one! - - - Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion! - - - If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or - an OperationOutcome resource, include the message in the exception message so that it will be - more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion! - - - Read invocations in the client now process the "Content-Location" header and use it to - populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion! - - - Fix issue where vread invocations on server incorrectly get routed to instance history method if one is - defined. Thanks to Neal Acharya from UHN for surfacing this one! - - - Binary reads on a server not include the Content-Disposition header, to prevent HTML in binary - blobs from being used for nefarious purposes. See - FHIR Tracker Bug 3298]]> - for more information. - - - Support has been added for using an HTTP proxy for outgoing requests. - - - Fix: Primitive extensions declared against custom resource types - are encoded even if they have no value. Thanks to David Hay of Orion for - reporting this! - - - Fix: RESTful server deployed to a location where the URL to access it contained a - space (e.g. a WAR file with a space in the name) failed to work correctly. - Thanks to David Hay of Orion for reporting this! - -
        - - - BREAKING CHANGE:]]>: IdDt has been modified so that it - contains a partial or complete resource identity. Previously it contained - only the simple alphanumeric id of the resource (the part at the end of the "read" URL for - that resource) but it can now contain a complete URL or even a partial URL (e.g. "Patient/123") - and can optionally contain a version (e.g. "Patient/123/_history/456"). New methods have - been added to this datatype which provide just the numeric portion. See the JavaDoc - for more information. - - - API CHANGE:]]>: Most elements in the HAPI FHIR model contain - a getId() and setId() method. This method is confusing because it is only actually used - for IDREF elements (which are rare) but its name makes it easy to confuse with more - important identifiers. For this reason, these methods have been deprecated and replaced with - get/setElementSpecificId() methods. The old methods will be removed at some point. Resource - types are unchanged and retain their get/setId methods. - - - Allow use of QuantityDt as a service parameter to support the "quantity" type. Previously - QuantityDt did not implement IQueryParameterType so it was not valid, and there was no way to - support quantity search parameters on the server (e.g. Observation.value-quantity) - - - Introduce StringParameter type which can be used as a RESTful operation search parameter - type. StringParameter allows ":exact" matches to be specified in clients, and handled in servers. - - - Parsers (XML/JSON) now support deleted entries in bundles - - - Transaction method now supported in servers - - - Support for Binary resources added (in servers, clients, parsers, etc.) - - - Support for Query resources fixed (in parser) - - - Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a - resource) - now parse and encode correctly, meaning that all contained resources are placed in the "contained" element - of the root resource, and the parser looks in the root resource for all container levels when stitching - contained resources back together. - - - Server methods with @Include parameter would sometimes fail when no _include was actually - specified in query strings. - - - Client requests for IdentifierDt types (such as Patient.identifier) did not create the correct - query string if the system is null. - - - Add support for paging responses from RESTful servers. - - - Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are - produced by the Health Intersections server) - - - Server now automatically compresses responses if the client indicates support - - - Server failed to support optional parameters when type is String and :exact qualifier is used - - - Read method in client correctly populated resource ID in returned object - - - Support added for deleted-entry by/name, by/email, and comment from Tombstones spec - - - - - - - - - -
        diff --git a/src/changes/changes.xsd b/src/changes/changes.xsd deleted file mode 100644 index 9478c44f0d8..00000000000 --- a/src/changes/changes.xsd +++ /dev/null @@ -1,263 +0,0 @@ - - - - - 1.0.0 - - Record every release with their subsequent changes. - - - - - - 1.0.0 - - Record every release with their subsequent changes. - - - - - - 1.0.0 - - Contains the properties of this document. - - - - - - 1.0.0 - - Contains the releases of this project with the actions taken - for each of the releases. - - - - - - - - 1.0.0 - - - - - 1.0.0 - The list of releases for this project. - - - - - - - 1.0.0 - A single release of this project. - - - - - 1.0.0 - The list of actions taken for this release. - - - - - - 1.0.0 - - The version number associated with this release. - - - - - - 1.0.0 - - - <p>The date of this release.</p> - <p>This field can be any String, such as "in SVN" when the version isn't yet released. </p> - - - - - - - 1.0.0 - - A short description of this release. - - - - - - - 1.0.0 - - A single action done on the project, during this release. - - - - - - 1.0.0 - A list of fix issues. - - - - - 1.0.0 - A list of contibutors for this issue. - - - - - - 1.0.0 - - - <p>Name of developer who committed the change.</p> - <p>This <b>MUST</b> be the name of the developer as described in the developers section of the pom.xml file.</p> - - - - - - - 1.0.0 - - Name of the person to be credited for this change. This can be used when a patch is submitted by a non-committer. - - - - - - 1.0.0 - - Email of the person to be credited for this change. - - - - - - 1.0.0 - - - <p>Id of the issue related to this change. This is the id in your issue tracking system.</p> - <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplate parameter.</p> - <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p> - - - - - - - 1.0.0 - - - Supported action types are the following: - <ul> - <li>add : added functionnality to the project.</li> - <li>fix : bug fix for the project.</li> - <li>update : updated some part of the project.</li> - <li>remove : removed some functionnality from the project.</li> - </ul> - - - - - - - 1.0.0 - - - <p>Id of issue tracking system. If empty 'default' value will be use.</p> - <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplatePerSystem parameter.</p> - <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p> - - - - - - - 1.0.0 - fix date - - - - - - 1.0.0 - - A fixed issue. - - - - - 1.0.0 - - - <p>Id of the issue related to this change. This is the id in your issue tracking system.</p> - <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplate parameter.</p> - <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p> - - - - - - - - 1.0.0 - - Name and Email of the person to be credited for this change. This can be used when a patch is submitted by a non-committer. - - - - - 1.0.0 - Name of the person to be credited for this change. - - - - - 1.0.0 - Email of the person to be credited for this change. - - - - - - 1.0.0 - - - - - 1.0.0 - Page Title. - - - - - 1.0.0 - Page Author - - - - - - - 1.0.0 - - A description of the author page. - - - - - - 1.0.0 - - The page author email. - - - - - \ No newline at end of file diff --git a/src/site/fml/hapi-fhir-faq.fml b/src/site/fml/hapi-fhir-faq.fml deleted file mode 100644 index 36162c4b927..00000000000 --- a/src/site/fml/hapi-fhir-faq.fml +++ /dev/null @@ -1,102 +0,0 @@ - - - - Getting Help - - Where can I ask questions or get help? - -

        - Please see [this page](https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help) in the HAPI FHIR Wiki for information on getting help. -

        -
        -
        -
        - - Using HAPI - - - What JDK version does HAPI support? - - -

        - HAPI supports JDK 1.6 for the entire library, except for the CLI tool which is 1.8. -

        -

        - Note that the HAPI library itself also requires a 1.8 JDK to build, since the unit tests - have JDK 1.8 dependencies. -

        -
        -
        -
        - - JPA Server - - - I would like to connect to the Derby database using a JDBC database browser (e.g. Squirrel, Toad, DBVisualizer) - so that I can access the underlying tables. How do I do that? - - -

        - By default Derby doesn't actually open any TCP ports for you to connect externally to it. - Being an embedded database, it works a bit differently than other databases in that the - client actually is the database and there's no outside communication with it possible. -

        -

        - There are a few options available to work around this fact: -

          -
        • - The easiest thing is to just load your data using the FHIR API. E.g. you can - use HTTP/REST creates, transactions, etc to load data into your database directly. -
        • -
        • - If you want to access the underlying database, the next easiest thing is to configure - the database to use a filesystem directory, e.g. - "jdbc:derby:directory:target/jpaserver_derby_files;create=true". You can - then shut the server down and use that same URL to connect a derby client (e.g. - Squirrel or DBVisualizer) to the same path. You may need to use a fully qualified - path instead of a relative one though. -
        • -
        • - Another option is to use a different database (e.g. MySQL, Postgres, Oracle, etc.). - HAPI's JPA server is based on JPA/Hibernate so it will support any database platform - that hibernate supports. -
        • -
        • - A final option is to start up Derby in network mode. Doing this is a bit more - involved since you need to start the derby server separately, and then use a special - URL to connect to it. You can find an example of how to start network Derby - here - and - an example of setting up a datasource - here. -
        • -
        -

        -
        -
        -
        - - Contributing - - - My build is failing with the following error: - [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test (default-test) on project hapi-fhir-jpaserver-base: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.19.1:test failed: The forked VM terminated without properly saying goodbye. VM crash or System.exit called? - - -

        - This typically means that your build is running out of memory. HAPI's unit tests execute by - default in multiple threads (the thread count is determined by the number of CPU cores available) - so in an environment with lots of cores but not enough RAM, you may run out. If you are getting - this error, try executing the build with the following arguments: -

        -
        mvn -P ALLMODULES,NOPARALLEL install
        -

        - See Hacking HAPI FHIR for more information on - the build process. -

        -
        -
        -
        -
        diff --git a/src/site/imgsources/hapi-1.1-structs-datatypes.xml b/src/site/imgsources/hapi-1.1-structs-datatypes.xml deleted file mode 100644 index 04e57051bef..00000000000 --- a/src/site/imgsources/hapi-1.1-structs-datatypes.xml +++ /dev/null @@ -1,2 +0,0 @@ - -7VhRb9owEP41PDaCpAnwWKC0L5sqMWnbo5s4iVXHRo5TYL++5+RMAgEadcCEtBeU+2Jf7O++O5/pedNs/aTIMv0mI8p7bj9a97xZz3UDdwC/BthUwL03rIBEsaiCcIQBFuwPRbCPaMEimu8M1FJyzZa7YCiFoKHewWLJ8RPobEkS674GFiHhbfQni3RaoSM3qPFnypLUfmYQjKs3ud5YHxGNScH1XQnBO/M6I9ZXuRDvEQhTUoIb85Stp5Qb0iwhHvViL/Djse+Ho8EwvKuWP+86fLsNRQUu9a9dIoPvhBe40YVWTCQz3WJglTJNF0sSGnsFquh5k1RnHKwBPMZS6AWOLm3G+VRyqcrZ3ng8n4/HgHfYBG70nSpNUXAlhJt6ojKjWm1giH07xH2gGgc+2qs63AMPsbQRaqsggqpLtq5rFuEBiexKql1/TWpInCIVTpwy5WQmlxzDgWIZ0+ydOl/l/CpcWvszLu8vwiWmWkugPzZLeksS9YIOErUUXl6iXotWqRIn5cNKo0zkmoiQGplWev0661dh85BID7F5GZHiQdJg87nIiPhOMnrbhdT9t4UUe4rPCmmU68J1QpktZQ7UAhRwWNfkVcFTYp5OhaNB/aHIXIXmrjXWhuO8NI+Oy/eWS+xB8V6vxGIP2b3Efpn0q5DZtcJeRKI2Qg1OaAQ9PppS6VQmUhD+WKMTJQsRUeOyv8sXXTP9y8COj9ZvHERF9KCUXIEZMZJJAbMNOAcZ28nCXmegz0PBN2xOXimfSBVRZXUvpDDL6R6lXBaqDPWpw0YTldCjjtCTIelktBXlxLSgOxelQ6HDqS+Sweq3KnFtkbIqsRcK66JaJc6qBQAkE/N5O2xpBuQtiWwX2kk1PiXxKPCGw6HrD4gfb0VfZ2BLRXlKoKPxZmGh+GaiSPhmOD14EDQEVCvrYl24TS3b4Fi7kX42KZrZNzpD9rV4bPfgL/bq0j5qZ0QTDW0iRHP/VYt9IMPgDWZzreQb3cuc5iGCEOEsEWCqausTwyyDG/8D4hmLorIIHIqkhNExL7M8hXEUJlwkiPtNqrUbMbTxOneP2ophu+G/2VzYEvRJLowvweN9i8ep7Tv/58KJIO7HMPDb7UT/WsmA94oj3QTSepX24VxNAbbwp5sCrAHnawqOBADM+m/Q6iCv/0T2Hj8A \ No newline at end of file diff --git a/src/site/imgsources/hapi-1.1-structs-resource.xml b/src/site/imgsources/hapi-1.1-structs-resource.xml deleted file mode 100644 index 86ab1ae797d..00000000000 --- a/src/site/imgsources/hapi-1.1-structs-resource.xml +++ /dev/null @@ -1,2 +0,0 @@ - -3Vnfb6M4EP5rIt09LApQ8uOxpM1upTupak+6u0cXDFh1cGScbXN//Y7xODhAu+k2Ie29RPjzD+xvvvHMkFG4WD1/lWRd/ClSykfBOH0ehVejIJgEPvxqYGuAi3BqgFyy1EA4QgP37D+K4BjRDUtptTdQCcEVW++DiShLmqg9LBMcX4GLrUlul2+A+4TwLvo3S1Vh0FkwafBvlOWFfY0/mZueSm3tGinNyIarLzUEfbp7Rexa9UbCayBMCgHL6KfV84JyTZolJKRhFk6ibB5FycyfJl/M9peHDt8dQ9ISt/reJWdmye+Eb/CcNzGp6B2txEYmtMPCU8EUvV8T6AqvnkAZozAu1IpDy4fHTJTqHkfXbcb5QnAh69nh1cW1v4wAx7dSqSgK6pCz1WPwYF+pWFEltzAHVwgmqAZUZITNp8bi/gVihWNtKyKCwst3KzdEwgNyeSCvqB+HVyFzr+BTLyuY9FhZKVIm1NMi0q7lkTXz3k+9cTNftysYycr8D5rpcwJvFomFUmK1j90hHQ70l4AX1MAQxpoeaCyLHdVY9k5yvaAxw/i3b5e3N7+PggmHTcQPEp5y/fSZnMO3BCPffp93hEN5Bwi0TXhCvE1RGu8wHqFfqp3iVx1iEBr7dNtH42l0i2HI1e1luXWle/fZhRu2Lope4Q52rfvhL93re0Y5v4o7nJ739r3ocEJTSN+wKaQqRC5Kwq8bNJZiU6ZULzne54s+M/WPhj2tSN36FwcBW3JruoK6T7d1Zz2tTC+lFE/QfOAieTTQEoS+m22T2Dq6mi3rfb7RKHBOo4PXQ5EiMqcvLVQnF13rSsqJYt/3d3RcU0WDm2r6cU2Ft+8HNRXWNs5NdQuv1EceLKOZz5fL+XygUNyb7w+X0WD1+7OMJq3UJvAkBgNvZ5IW7w7HfSYYhM/zpjbdwtSR7xBZzUnF247A5y1W/TdXqy/p9gMkM+ctJa19Th8h3xYE218HOHmgPBYypdJqvhSl3szJwiUGpFfDpSkM3xEvceqtYPVNYb822HvLasSmWXYJsymc1ZgfKCb69XbYWg+oOgLZbfQgzUSUZLNJOJ1Og8gnUWbZc7yvo6GqIGv9mGwk38aSJI+awt7Y4Min0dXJ6mHrRPYrcNf3rEe4rufPjuB7HR671XBTdLWDxU2pqMyAOGNNl2ugR49weKyUFI+05SduuECIcJaX0JTmpLHmmsFX6EvEVyxNa4fvs5uA0RmvPbqAcRQmnMRk7drPuoJjMmudY9+WHYt1q+lPq/xDpX8S5WMF3av88Q1Gb3hccFJVWvI/zZ3+507QNp51CjdnGA/lBVg6vJAyIK0D5Agni/yYy78e+eu74PiVMjSbf+RMtG7+zwyvfwA= \ No newline at end of file diff --git a/src/site/imgsources/hapi-usage.vsdx b/src/site/imgsources/hapi-usage.vsdx deleted file mode 100644 index bb803636c11..00000000000 Binary files a/src/site/imgsources/hapi-usage.vsdx and /dev/null differ diff --git a/src/site/imgsources/hapi_banner.xcf b/src/site/imgsources/hapi_banner.xcf deleted file mode 100644 index 9152a8c9dfa..00000000000 Binary files a/src/site/imgsources/hapi_banner.xcf and /dev/null differ diff --git a/src/site/imgsources/hapi_fhir_banner.xcf b/src/site/imgsources/hapi_fhir_banner.xcf deleted file mode 100644 index c44258c7335..00000000000 Binary files a/src/site/imgsources/hapi_fhir_banner.xcf and /dev/null differ diff --git a/src/site/imgsources/hapi_fhir_banner_right.xcf b/src/site/imgsources/hapi_fhir_banner_right.xcf deleted file mode 100644 index 7ec063120e0..00000000000 Binary files a/src/site/imgsources/hapi_fhir_banner_right.xcf and /dev/null differ diff --git a/src/site/imgsources/hapi_hoh_banner.xcf b/src/site/imgsources/hapi_hoh_banner.xcf deleted file mode 100644 index d6c4d3b1961..00000000000 Binary files a/src/site/imgsources/hapi_hoh_banner.xcf and /dev/null differ diff --git a/src/site/imgsources/hapi_testpanel_banner.xcf b/src/site/imgsources/hapi_testpanel_banner.xcf deleted file mode 100644 index c665ce02cec..00000000000 Binary files a/src/site/imgsources/hapi_testpanel_banner.xcf and /dev/null differ diff --git a/src/site/imgsources/restful-server-interceptors-exception.xml b/src/site/imgsources/restful-server-interceptors-exception.xml deleted file mode 100644 index d322c998c99..00000000000 --- a/src/site/imgsources/restful-server-interceptors-exception.xml +++ /dev/null @@ -1 +0,0 @@ -3VlNb9s4EP01PsaQTH/pWCd2d4EWCOoFdvfIWJRFlBa1FJU4/fUdWkN9UbEVR2m79cEwh+SIfPPmcUSPyO3h+FHRNP4sQyZGEy88jsjdaDKZz2fwbQzPhWE69QvDXvGwMNUMW/6NodFDa85DljUGaimF5mnTuJNJwna6YYukaD4ipXvrvjJsd1S41r95qOPCupzMK/sfjO9j+xh/HhQ9mX62PkIW0Vzom5MJ+kz3gVpfp12RNQCmpAQ35tfheMuEAc0CUmx980JvuUjFElzI+Qm4iEcqclzjl/X2ryiHMbAxcqQjstoy9ciUs5kspqn5mR/EJx4xwRNorVKm+IFpGE/uBJrvK9sqK6I4nZvfWsmv7FYKaUbD4zz4bGCp2GNxBlRWEReiNjIINvBxd4sAwHo1Q5qdTLj7j0zCQtQzDLG9MyQTchDx8p6qGPse2uJafMkCjRS5ti9dV3DDD0S8G33ioP9nAjDtWKpho78n4JPllYDPBwB86tKdZTIHxGHmvaA8qfH+XslHEBgIRGn6zHQsw980MlML+8+IzMKJjIMyTABlN8havDNNld5qqo2xBYsBEFbbAW0UYc8AmFkVQMgst2uQkS7EBgBs2aEdO3ngyR6sX9h/Ocu0C2EIZxo2mXiQT+vKsDoZoCOWin+Tiabw6JWBgsMp+EHwfQKdD1JreTCjk/CDUqcJMmXQZSx4Ri/Pwg5oq+d/wOiNZ7b576lJbLOWJneIVUeYMHPr/DEbfEPcugKCPu4lh8dXMtZKlgBrGesBmLlnGie1oloup1egS1LWRQvD6/HMUIsmoWCuLCmZJyHYyZ0PwD7FXLNtSk+IPUFBNgD9SdBEoTxN65phdWToDOgoXs4SPpEnWabIZMEiWMwZNs6JUdsOPoLxyHVtDrQaU6Ddj8EX6XpJe0ujYoJq/tgsVq+nsz/1xv4sqD6YX9ZjkXoOvR2/01Z5dcnvgGnjnvVDEN6S2Z6RAXqtBaqL71af3sR3lJjajorMXx9NxcglFC/X6L2V+J5q/yDk7uuZHJgujPy7OdDKvJdiUTCgGcO+qg794wWiVAVobIG7Ph2MF1zLhQy4hqn4ClmLq2I6VxBOTytjwkrlPQIb0iwuj4hLZ3qvk/iXCRrx8Q38HcTlqnLVgHl1sdorea4uXIndwA+oXDtKV3gLS2UCYJ3juVTw8rWXCRUNsrvKVHLa4GYa91SDChn0ABRveqYIKLKgLAGssnXpXDAzjrp0Dpa0gfiiu+YLXGWpBXmzOQW5lyS+stCFVXveJAiWE8/3FgFpJtqNj1cQP6vOIGQx9ur1QCvr+9YZ8/Y1jr1bvJD7jqMbJGcJEOaF9SOjKINYvFE+UJheJx87IXNw0FHIk1WsD5ZvQ1yKtfS4jH6NJhbwodXB+qhho2OT38D5969zhjkO+yYnmTlHn78gQ9Qrtv1a+rfrdcfRcGUPcd9rX+BpB8b9C3TLpx9QoMPLX3tHqWJwjbhjWfb/LdJbbG6eRzaK/SPUVe8RbwjSgxe4Da0+KOXvQF334r6s2JPcTLuyYu939fYqkaoYAAhXFKhOCie89WL+VwrvbKi7AmhW/7AVw6v/J8n6Ow== \ No newline at end of file diff --git a/src/site/imgsources/restful-server-interceptors.xml b/src/site/imgsources/restful-server-interceptors.xml deleted file mode 100644 index 530869baeba..00000000000 --- a/src/site/imgsources/restful-server-interceptors.xml +++ /dev/null @@ -1 +0,0 @@ -3Vpdb+I6E/41vdwq4JDC5dKW7SudldBS6Zxz6RIHrDUxr2P6sb9+x2RMPuzQFALLKReIjJ2J/cwzj8cOV+R29fpN0fXyu4yZuOoH8esVubvq96NoAN/G8JYbwrCXGxaKx7mpZJjxXwyNAVo3PGZZpaOWUmi+rhrnMk3ZXFdsiRTVR6zpwrovDLM5Fa71bx7rZW4d9qPC/sD4Ymkf04tGeUum36yPmCV0I/SXrQnaTPOKWl/bWZF7AExJCW7Mr9XrLRMGNAtIPvVJQ+tukIqlOJD9N+AgnqnY4Bh/3M8ekw30gYmRV3pFxjOmnplyJpMt6dr83KzEXzxhgqdwNV4zxVdMQ39yJ9A8LWzjLI9iGJnfWsmf7FYKaXrD4wL4TGCo2GJxBlTGCRei1HM0msDHnS0CAOPVDGm2NeHsvzEJA1Fv0MW2DpBMyEHEK3gpYtwL0LYsxTccopEi1xY71wXc8AMR96NPHPT/lwJMc7bWMNHPCXjfAvcnAA9durNMbgBxuHMqKE9LvJ8q+QwCA4HYmb4zvZTxJ41MaGH/aGRGHUTmxomMgzLcAMpukLV4Z5oqPdNUG2MNFgMgjNYDbZJgSweY3VQhsxwtQUY8iFnbMYANHcAeHh+nYPnB/r9hmXbhi2E9w0smnuTLfWEYbw3QsJSK/5KppvDYsYGBwwr4VfBFCo1PUmu5Mr3T+KtS2xvkmkGTseD6PNwLOSCt3v4BY3A9sJf/bi+JvSylyB3i5AkRZm2ZO2aCR8TMFwz0MZUcHl9IWC1RRljHWA/AygXTeFMtorvhtAqyrXSctDiKtMRmNo6/Zx9Toq3tUqatBfoY2vbsaIsZ8XQuVzxdIHGnioHyzlmWMVdpW5HY8rYlhZ+EnP80pleuS9SEq5yZ4Y3htLmuUpOMKXoVLMF888QlZ0M1nm2pCu3XN8iuIljX/RrhPk5Z4wVWjuKDcmI95unVCYXdElMxvVGw0gZaGRPq8CmiHNNsCSwid70WqtVKay4ngqSWjR2qjq9MwkUl4JnZUQmaZTzhngxVcpPGO8xfllyz2Zpu4XuBXWAXJXtYQyLyFCo+/epi2e1h4E6ryL7a62SKjDvYPYosjSp/VknGgB6R0BCaDhLaeDmTJLvF9n9aki8mguHJJHknKY2SvKRpLP6IHhO7/0MYiC2wz6HHnhOsvdxN5XZvXpGJfduSiJgtt2dj0ixOeIsjTk1Iv8vb9zbgO6Nigmr+XD2xPJzPJCLXvUFJkWr0blAkx29YO2N7z2+HaeOesB3CjoZAD2Hp9K9CzXzCe3x8KinjxVOlHtIwGF4HN6WQHsaUOgPfcdshUdyS9wRlXUhwjTlDWWeXs9KM5EYvACso67K1TOEk7ZOVcjaIRxQCEKEOCgHjBcfSffVm3zh9kurtcoIWYcqfQF0OOt02YB58tt0qeQ4+5w4jXFjPcNC976S7hY5JBa9sFjKlosJ5V6B21LZr8ZRqECMDImAThHuqxjwZdmu8FTif3I0GxpG/ZognEGZ0V33tU1hKsZ5MtrFulWQesfzgqTmULhUKfMHLc1Ucg3rFYRfOj9YYUb0arTtqyPvCke0okyQDOI/UBqxp9mkDKEK85arhQUPqejjQui4h9sTvDHUJafMC4KFhM3vKAqWW3B7Ma6nyEcRh2vVVi/SiDlYt8HJYGrgcqDnqbvmD/XBDxXIxpUrH0XZrFEixLqJt/yHVWquaol131F19St59ewBA+g6SW51UHaV79TcHsBac7KQKLou/ceUwFn+CI/e/AQ== \ No newline at end of file diff --git a/src/site/imgsources/what_are_these.txt b/src/site/imgsources/what_are_these.txt deleted file mode 100644 index 236ddd9361e..00000000000 --- a/src/site/imgsources/what_are_these.txt +++ /dev/null @@ -1,4 +0,0 @@ -In this directory: - -* XCF files are opened in The GIMP -* XML files are opened in draw.io diff --git a/src/site/imgsources/who_uses_hapi.xcf b/src/site/imgsources/who_uses_hapi.xcf deleted file mode 100644 index e11ffc1de15..00000000000 Binary files a/src/site/imgsources/who_uses_hapi.xcf and /dev/null differ diff --git a/src/site/markdown/doc_jdk9_guide.md b/src/site/markdown/doc_jdk9_guide.md deleted file mode 100644 index 7e5994d2f13..00000000000 --- a/src/site/markdown/doc_jdk9_guide.md +++ /dev/null @@ -1,3 +0,0 @@ -# Using HAPI FHIR with JDK9 / JDK10+ - - diff --git a/src/site/markdown/doc_vagrant.md b/src/site/markdown/doc_vagrant.md deleted file mode 100644 index cdf2e9ee8d6..00000000000 --- a/src/site/markdown/doc_vagrant.md +++ /dev/null @@ -1,54 +0,0 @@ -Creating your own demo server with Vagrant -======== -This source code repository includes configuration files to materialize an _entire_ server implementation off all project build artifacts in a single virtual machine (VM) image from scratch, allowing you to quickly bootstrap your own local copy of the project. The server will be completely encapsulated within the created VM image. The process _should_ run on OSX, Linux and Windows, but YMMV. The built-in settings support creation of a *VirtualBox*-based image on Ubuntu Linux, though with tuning of the base image you should be able to create images suitable for other hypervisors and cloud-based IaaS providers such as VMware and Amazon Web Services (AWS), respectively. - -Dependencies ----- - -Prior to running, please ensure you have all .war files built, and the following installed and functioning propertly. - - * All normal Java development dependencies. (Java SDK and Maven 3, specifically.) - * VirtualBox - * Vagrant - - -Creating Your VM ----- - - cd hapi-fhir-root/ - mvn install # Creates web application .war files. Make sure they're built before proceeding! - cd vagrant - vagrant up # Will take a few minutes to boot up. - -Your new server environment should now be running in a headless virtual machine on your local computer. The following step are performed automatically for you within the VM sandbox environment: - - * A complete Ubuntu 14.04 Server VM is launched in headless mode, bridged to whatever host network interface you've selected. - * An IPv4 address is assigned via DHCP. - * MySQL Server (Community Edition) is installed from the official 10gen repository. (See the [Vagrantfile](https://github.com/preston/hapi-fhir/blob/master/vagrant/Vagrantfile) for the default root password.) - * Oracle Java 8 is installed. - * Tomcat 7 is installed and configured as a system service. - * All compiled *.war applications are deployed automatically and started. - * A "fhir" user is added to tomcat-users.xml. See [fhir.json](https://github.com/preston/hapi-fhir/blob/master/vagrant/chef/data_bags/tomcat_users/fhir.json) for the default password. - -Tomcat will now be running on the VM on port 8080 with the management GUI available. For example, you can now visit: - - * *Tomcat Manager*: assigned_ip:8080/manager/html - * *HAPI FHIR* JPA Server: assigned_ip:8080/hapi-fhir-jpaserver/ - -Screenshots ----- -![Tomcat Manager](https://raw.githubusercontent.com/preston/hapi-fhir/master/vagrant/screenshots/tomcat.png) - -![Demo Server](https://raw.githubusercontent.com/preston/hapi-fhir/master/vagrant/screenshots/hapi-fhir-jpaserver.png) - -Advanced Configuration ----- -The Vagrant documentation is the best place to start, but a few more commands of note are: - - vagrant ssh # Command-line access to the VM. - vagrant destoy # Shuts down and completely destroys the VM. - - -Credits ----- -Vagrant and Chef configuration by Preston Lee diff --git a/src/site/resources/CNAME b/src/site/resources/CNAME deleted file mode 100644 index 507c033b8e4..00000000000 --- a/src/site/resources/CNAME +++ /dev/null @@ -1 +0,0 @@ -hapifhir.io diff --git a/src/site/resources/css/bootstrap-responsive.min.css b/src/site/resources/css/bootstrap-responsive.min.css deleted file mode 100644 index 291c98f54b6..00000000000 --- a/src/site/resources/css/bootstrap-responsive.min.css +++ /dev/null @@ -1,20 +0,0 @@ -/*! - * Bootstrap Responsive v2.3.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ -.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} -.clearfix:after{clear:both;} -.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} -.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} -@-ms-viewport{width:device-width;}.hidden{display:none;visibility:hidden;} -.visible-phone{display:none !important;} -.visible-tablet{display:none !important;} -.hidden-desktop{display:none !important;} -.visible-desktop{display:inherit !important;} -@media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;} .visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;}}@media (max-width:767px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;} .visible-phone{display:inherit !important;} .hidden-phone{display:none !important;}}.visible-print{display:none !important;} -@media print{.visible-print{display:inherit !important;} .hidden-print{display:none !important;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} .thumbnails>li{float:none;margin-left:0;} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .row-fluid [class*="offset"]:first-child{margin-left:0;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;} .controls-row [class*="span"]+[class*="span"]{margin-left:0;} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade{top:-100px;} .modal.fade.in{top:20px;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:20px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px;} .media-object{margin-right:0;margin-left:0;} .modal{top:10px;left:10px;right:10px;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:20px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%;} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%;} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%;} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%;} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%;} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%;} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%;} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%;} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%;} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%;} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%;} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%;} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%;} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%;} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%;} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%;} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%;} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%;} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%;} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%;} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%;} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%;} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%;} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%;} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%;} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%;} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%;} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%;} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%;} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%;} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%;} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%;} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%;} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%;} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:20px;} input.span12,textarea.span12,.uneditable-input.span12{width:710px;} input.span11,textarea.span11,.uneditable-input.span11{width:648px;} input.span10,textarea.span10,.uneditable-input.span10{width:586px;} input.span9,textarea.span9,.uneditable-input.span9{width:524px;} input.span8,textarea.span8,.uneditable-input.span8{width:462px;} input.span7,textarea.span7,.uneditable-input.span7{width:400px;} input.span6,textarea.span6,.uneditable-input.span6{width:338px;} input.span5,textarea.span5,.uneditable-input.span5{width:276px;} input.span4,textarea.span4,.uneditable-input.span4{width:214px;} input.span3,textarea.span3,.uneditable-input.span3{width:152px;} input.span2,textarea.span2,.uneditable-input.span2{width:90px;} input.span1,textarea.span1,.uneditable-input.span1{width:28px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:30px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%;} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%;} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%;} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%;} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%;} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%;} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%;} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%;} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%;} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%;} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%;} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%;} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%;} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%;} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%;} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%;} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%;} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%;} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%;} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%;} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%;} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%;} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%;} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%;} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%;} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%;} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%;} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%;} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%;} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%;} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%;} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%;} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%;} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%;} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:30px;} input.span12,textarea.span12,.uneditable-input.span12{width:1156px;} input.span11,textarea.span11,.uneditable-input.span11{width:1056px;} input.span10,textarea.span10,.uneditable-input.span10{width:956px;} input.span9,textarea.span9,.uneditable-input.span9{width:856px;} input.span8,textarea.span8,.uneditable-input.span8{width:756px;} input.span7,textarea.span7,.uneditable-input.span7{width:656px;} input.span6,textarea.span6,.uneditable-input.span6{width:556px;} input.span5,textarea.span5,.uneditable-input.span5{width:456px;} input.span4,textarea.span4,.uneditable-input.span4{width:356px;} input.span3,textarea.span3,.uneditable-input.span3{width:256px;} input.span2,textarea.span2,.uneditable-input.span2{width:156px;} input.span1,textarea.span1,.uneditable-input.span1{width:56px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:20px;} .navbar-fixed-bottom{margin-top:20px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 10px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#777777;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2;} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999999;} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111111;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .open>.dropdown-menu{display:block;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111111;border-bottom-color:#111111;} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}} diff --git a/src/site/resources/hapi.css b/src/site/resources/hapi.css deleted file mode 100644 index 63fcf963adf..00000000000 --- a/src/site/resources/hapi.css +++ /dev/null @@ -1,232 +0,0 @@ -/* -#bodyColumn { - width: 78.5% !important; -} - -#breadcrumbs { - background: white; - padding: 0px; - height: 40px; -} - -#search-form { - width: 140px; -} - -#navcolumn { - background-color: white !important; -} - -#leftColumn { - border: none !important; - width: 19% !important; - margin: 0px; -} - -#navcolumn h5 { - color: #806020; - background-color: #FFF0C0; - border-bottom: 1px solid #E3701A; -} - -#navcolumn li.expanded { - background: url("") !important; -} - -.nobr { - white-space: nowrap; -} - -.section { - padding: 0px; -} - -*/ - -.doc_info_bubble { - border: 1px dashed #808080; - border-radius: 6px; - padding: 8px; - margin: 8px; - background: #EEE; -} - -TABLE.pagenavlinks { - border: 0px; -} -A.pagenavlinks { - background: #CCC; - border-style: solid; - border-width: 1px; - border-color: #888; - border-radius: 7px; - padding: 7px; -} - -.navbar .brand { - color: #FF8; -} - -.well { - padding: 5px; -} - -.nav-list { - padding-right: 0px; - font-size: 12px; -} - -.nav-list LI { - line-height: 15px; -} - -/* -.nav-list .divider { - display: none; -} -*/ - -@media (min-width: 980px) { /* 768 980 */ - body { - padding-top: 40px; - } -} - -body { - padding-bottom: 10px; -} - -pre { - padding: 3px; - margin: 0 0 10px; - font-size: 0.8em; - line-height: 1.0em; -} - -.page-header { - padding-bottom: 2px; - margin: 0px 0 0px; - border-bottom: none; -} -h1[id]:before, -h2[id]:before, -h3[id]:before, -h4[id]:before, -h5[id]:before, -h6[id]:before, -a[name]:before { - height: auto; - margin: auto; -} - -DIV.main-body DIV.row DIV.span8 DIV.body-content { - top: -8px; - position: relative; -} - -.section h2 { - border-bottom: 2px solid #CF4711; - background-color: #FF7741; - color: #FFA; - font-size: 1.5em; - line-height: 1.4em; - border-radius: 6px; - padding-left: 5px; - padding-right: 5px; -} - -.section h3 { - border-bottom-width: 1px; - border-bottom-style: solid; - border-bottom-color: #993300; - width: 66%; - background-image: url('images/littlehapiface.png'); - background-repeat: no-repeat; - padding-left: 25px; - font-size: 1.3em; - line-height: 1.3em; -} - -.syntaxhighlighter { - font-size: 0.85em !important; -} - -.syntaxhighlighter .line.alt1, -.syntaxhighlighter .line.alt2 -{ - background-color: #F8F8F8 !important; -} - -.syntaxhighlighter .code .container:before { - display: block; -} - -.table th, .table td { - padding: 2px; -} - -tt { - white-space: pre; - color: #846; - margin-bottom: 5px; - margin-top: 10px; - padding: 2px; - border: 1px solid #AAA; - background-color: #F0F0F0; -} - -h1,h2,h3,h4,h5 { - color: #E3701A; - font-weight: bold; -} - -h4 { - font-size: 1.2em; - padding: 0px; - margin-bottom: 0px; - margin-top: 20px; -} - -li.expanded ul { - border-left: 2px solid #C0FFC0; - margin: 0px 0px 4px 0px !important; -} - -li.expanded ul li { - padding-left: 2px !important; -} - -dfn { - color: #008000; - background-color: #E0FFE0; - border-bottom: 1px dotted #40A040; -} - -/* -a,a.externalLink,a:active,a:hover,a:link,a:visited { - color: #993300; -} - -DIV.sidebar-nav UL LI UL LI { - font-size: 0.9em; -} -*/ - -.pull-left { - float: left; - display: inline-block; -} - -.pull-right { - float: right; - display: inline-block; -} - -.span12 { - width: 100%; -} - -.container-fluid { - padding-right: 0px; - padding-left: 0px; -} diff --git a/src/site/resources/images/favicon.png b/src/site/resources/images/favicon.png deleted file mode 100644 index f96cc5a3e6a..00000000000 Binary files a/src/site/resources/images/favicon.png and /dev/null differ diff --git a/src/site/resources/images/github-logo-mini.png b/src/site/resources/images/github-logo-mini.png deleted file mode 100644 index 815160af4cb..00000000000 Binary files a/src/site/resources/images/github-logo-mini.png and /dev/null differ diff --git a/src/site/resources/images/hacking_import.png b/src/site/resources/images/hacking_import.png deleted file mode 100644 index 1ec8496616f..00000000000 Binary files a/src/site/resources/images/hacking_import.png and /dev/null differ diff --git a/src/site/resources/images/hacking_import_step2.png b/src/site/resources/images/hacking_import_step2.png deleted file mode 100644 index 71c52ea430c..00000000000 Binary files a/src/site/resources/images/hacking_import_step2.png and /dev/null differ diff --git a/src/site/resources/images/hapi-1.1-structs-datatypes.svg b/src/site/resources/images/hapi-1.1-structs-datatypes.svg deleted file mode 100644 index 8ae16122ad2..00000000000 --- a/src/site/resources/images/hapi-1.1-structs-datatypes.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
        StringDt
        [Not supported by viewer]
        ca.uhn.fhir.model. primitive.StringDt
        [Not supported by viewer]
        StringType
        [Not supported by viewer]
        org.hl7.fhir.dstu3. model.StringType
        [Not supported by viewer]
        HumanNameDt
        [Not supported by viewer]
        ca.uhn.fhir.model. dstu2.composite.
        HumanNameDt
        [Not supported by viewer]
        HumanName
        [Not supported by viewer]
        org.hl7.fhir.dstu3. model.HumanName
        [Not supported by viewer]
        Primitive
        Datatypes
        [Not supported by viewer]
        Composite
        Datatypes
        [Not supported by viewer]
        diff --git a/src/site/resources/images/hapi-1.1-structs-resource.svg b/src/site/resources/images/hapi-1.1-structs-resource.svg deleted file mode 100644 index a737fe63e9f..00000000000 --- a/src/site/resources/images/hapi-1.1-structs-resource.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
        IBaseResource
        [Not supported by viewer]
        org.hl7.fhir.instance. model.api.IBaseResource
        [Not supported by viewer]
        IResource (HAPI)
        [Not supported by viewer]
        ca.uhn.fhir.model. api.IResource
        [Not supported by viewer]
        IAnyResource (RI)
        [Not supported by viewer]
        org.hl7.fhir.instance. model.api.IAnyResource
        [Not supported by viewer]
        Patient (HAPI)
        [Not supported by viewer]
        ca.uhn.fhir.model. dstu2.resource.Patient
        [Not supported by viewer]
        Patient (RI)
        [Not supported by viewer]
        org.hl7.fhir.dstu3. model.Patient
        [Not supported by viewer]
        Resource
        Interfaces
        [Not supported by viewer]
        Resource Instance Classes
        [Not supported by viewer]
        diff --git a/src/site/resources/images/hapi-fhir-cli-run-server.png b/src/site/resources/images/hapi-fhir-cli-run-server.png deleted file mode 100644 index a84018df579..00000000000 Binary files a/src/site/resources/images/hapi-fhir-cli-run-server.png and /dev/null differ diff --git a/src/site/resources/images/hapi-fhir-cli.png b/src/site/resources/images/hapi-fhir-cli.png deleted file mode 100644 index d503f80ed09..00000000000 Binary files a/src/site/resources/images/hapi-fhir-cli.png and /dev/null differ diff --git a/src/site/resources/images/hapi-usage.png b/src/site/resources/images/hapi-usage.png deleted file mode 100644 index b6109c12694..00000000000 Binary files a/src/site/resources/images/hapi-usage.png and /dev/null differ diff --git a/src/site/resources/images/hapi_authorizationinterceptor_read_normal.svg b/src/site/resources/images/hapi_authorizationinterceptor_read_normal.svg deleted file mode 100644 index 5a4bac103c1..00000000000 --- a/src/site/resources/images/hapi_authorizationinterceptor_read_normal.svg +++ /dev/null @@ -1,2 +0,0 @@ - -
        RestfulServer
        RestfulServer
        Authorization
        Interceptor
        [Not supported by viewer]
        ResourceProvider
        (user code)
        ResourceProvider<div>(user code)</div>
        read/search/etc
        read/search/etc
        authorize?
        authorize?
        invoke
        invoke
        return
        return
        200 OK
        200 OK
        authorize?
        authorize?
        invoke
        invoke
        return
        return
        Successful
        Read
        [Not supported by viewer]
        Denied
        Read
        [Not supported by viewer]
        Return value is 
        checked before
        actually returning
        [Not supported by viewer]
        Client
        Client
        read/search/etc
        read/search/etc
        403 FORBIDDEN
        403 FORBIDDEN
        \ No newline at end of file diff --git a/src/site/resources/images/hapi_authorizationinterceptor_write_normal.svg b/src/site/resources/images/hapi_authorizationinterceptor_write_normal.svg deleted file mode 100644 index cb9aee69a3d..00000000000 --- a/src/site/resources/images/hapi_authorizationinterceptor_write_normal.svg +++ /dev/null @@ -1,2 +0,0 @@ - -
        RestfulServer
        RestfulServer
        Client
        Client
        write
        write
        Authorization
        Interceptor
        [Not supported by viewer]
        authorize?
        authorize?
        ResourceProvider
        (user code)
        ResourceProvider<div>(user code)</div>
        return
        return
        authorize?
        authorize?
        invoke
        invoke
        return
        return
        200 OK
        200 OK
        write
        write
        403 FORBIDDEN
        403 FORBIDDEN
        Successful
        Write
        [Not supported by viewer]
        Denied
        Write
        [Not supported by viewer]
        Operation is checked
        before passing
        to ResourceProvider
        [Not supported by viewer]
        \ No newline at end of file diff --git a/src/site/resources/images/hapi_fhir_banner.png b/src/site/resources/images/hapi_fhir_banner.png deleted file mode 100644 index c264336271e..00000000000 Binary files a/src/site/resources/images/hapi_fhir_banner.png and /dev/null differ diff --git a/src/site/resources/images/hapi_fhir_banner_right.png b/src/site/resources/images/hapi_fhir_banner_right.png deleted file mode 100644 index f40e438546a..00000000000 Binary files a/src/site/resources/images/hapi_fhir_banner_right.png and /dev/null differ diff --git a/src/site/resources/images/jpa_architecture.png b/src/site/resources/images/jpa_architecture.png deleted file mode 100644 index 9e82adfc7c1..00000000000 Binary files a/src/site/resources/images/jpa_architecture.png and /dev/null differ diff --git a/src/site/resources/images/littlehapiface.png b/src/site/resources/images/littlehapiface.png deleted file mode 100644 index 4215aecdf96..00000000000 Binary files a/src/site/resources/images/littlehapiface.png and /dev/null differ diff --git a/src/site/resources/images/littlehapiface2020.png b/src/site/resources/images/littlehapiface2020.png deleted file mode 100644 index e9f02033c3b..00000000000 Binary files a/src/site/resources/images/littlehapiface2020.png and /dev/null differ diff --git a/src/site/resources/images/logos/maven-feather.png b/src/site/resources/images/logos/maven-feather.png deleted file mode 100644 index b5ada836e9e..00000000000 Binary files a/src/site/resources/images/logos/maven-feather.png and /dev/null differ diff --git a/src/site/resources/images/maven-logo-mini.png b/src/site/resources/images/maven-logo-mini.png deleted file mode 100644 index aa47bd5226e..00000000000 Binary files a/src/site/resources/images/maven-logo-mini.png and /dev/null differ diff --git a/src/site/resources/svg/hapi-fhir-logging-complete.svg b/src/site/resources/svg/hapi-fhir-logging-complete.svg deleted file mode 100644 index ae06998393d..00000000000 --- a/src/site/resources/svg/hapi-fhir-logging-complete.svg +++ /dev/null @@ -1 +0,0 @@ -hapi-fhir
        slf4j-api
        [Not supported by viewer]
        commons-httpclient
        jcl-over-slf4j
        [Not supported by viewer]
        Underlying LoggingFramework(logback, log4j, etc.)
        \ No newline at end of file diff --git a/src/site/resources/svg/hapi-fhir-logging.svg b/src/site/resources/svg/hapi-fhir-logging.svg deleted file mode 100644 index ff4bc79aa79..00000000000 --- a/src/site/resources/svg/hapi-fhir-logging.svg +++ /dev/null @@ -1 +0,0 @@ -hapi-fhir
        slf4j-api
        [Not supported by viewer]
        logback-classic
        [Not supported by viewer]
        slf4j-log4j
        [Not supported by viewer]
        log4j
        [Not supported by viewer]
        slf4j-jdk14
        [Not supported by viewer]
        JDK Logging
        (built in to Java)
        [Not supported by viewer]
        disk
        [Not supported by viewer]
        disk
        [Not supported by viewer]
        disk
        [Not supported by viewer]
        Chosen by
        searching
        classpath
        [Not supported by viewer]
        \ No newline at end of file diff --git a/src/site/resources/svg/hapi_usage_patterns.svg b/src/site/resources/svg/hapi_usage_patterns.svg deleted file mode 100644 index 730b07d8e95..00000000000 --- a/src/site/resources/svg/hapi_usage_patterns.svg +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - External FHIR Clients -
        -
        - [Not supported by viewer] -
        -
        - - - -
        - Your -
        -
        Application
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI FHIR -
        - Client -
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI -
        - Model -
        - Objects -
        - [Not supported by viewer] -
        -
        - - - - -
        - External -
        - FHIR -
        - Server -
        -
        - [Not supported by viewer] -
        -
        - - - -
        HTTP
        - [Not supported by viewer] -
        -
        - - - - -
        - Use the HAPI FHIR client in an application to fetch from or store - resources to an external server. -
        - Learn Mode -
        - [Not supported by viewer] -
        -
        - - - -
        - Your -
        -
        Application
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI -
        - Model -
        - Objects -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI FHIR -
        - Server -
        - [Not supported by viewer] -
        -
        - - - -
        HTTP
        - [Not supported by viewer] -
        -
        - - - - -
        - Use the HAPI FHIR server in an application to allow external - applications to access or modify your application's data. -
        - Learn More -
        -
        - [Not supported by viewer] -
        -
        - - - -
        - HAPI JPA Database Server -
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI FHIR Server -
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - JPA Persistence Module -
        -
        - [Not supported by viewer] -
        -
        - - - -
        - Your -
        -
        Application
        -
        - [Not supported by viewer] -
        -
        - - - -
        HTTP
        - [Not supported by viewer] -
        -
        - - - - -
        - Use the HAPI JPA/Database Server to deploy a fully functional FHIR - server you can develop applications against. -
        - Learn More -
        - [Not supported by viewer] -
        -
        - - - -
        - Your -
        -
        Application
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI -
        - Model -
        - Objects -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI FHIR -
        - Parser (Xml/Json) -
        -
        - [Not supported by viewer] -
        -
        - - - -

        - [Not supported by viewer] -
        -
        - - - -
        - Raw FHIR Resources -
        -
        - [Not supported by viewer] -
        -
        - - - - - - - -
        - HAPI FHIR -
        - Parser (Xml/Json) -
        -
        - [Not supported by viewer] -
        -
        - - - - - -
        - HAPI -
        - Model -
        - Objects -
        - [Not supported by viewer] -
        -
        - - - -

        - [Not supported by viewer] -
        -
        - - - - -
        - Use the HAPI FHIR parser and encoder to convert between FHIR and - your application's data model. -
        - Learn More -
        - [Not supported by viewer] -
        -
        - - - - -
        -
        diff --git a/src/site/resources/svg/restful-server-interceptors-exception.svg b/src/site/resources/svg/restful-server-interceptors-exception.svg deleted file mode 100644 index b6e67f3c7df..00000000000 --- a/src/site/resources/svg/restful-server-interceptors-exception.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -RESTfulServerInterceptorResource/PlainProviderMethodIncoming Request
        Request is handled
        [Not supported by viewer]
        handleExceptionreturn true;Responsethrow exceptionpreProcessExceptionreturn null;
        \ No newline at end of file diff --git a/src/site/resources/svg/restful-server-interceptors.svg b/src/site/resources/svg/restful-server-interceptors.svg deleted file mode 100644 index 0f874ea368a..00000000000 --- a/src/site/resources/svg/restful-server-interceptors.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -RESTfulServerInterceptorResource/PlainProviderMethodHTTP RequestincomingRequestPreProcessedreturn true;
        Request is classified
        [Not supported by viewer]
        incomingRequestPostProcessedreturn true;
        Request is handled
        [Not supported by viewer]
        outgoingResponsereturn true;HTTP ResponseincomingRequestPreHandledreturn;
        Request is parsed
        [Not supported by viewer]
        \ No newline at end of file diff --git a/src/site/resources/syntaxhighlighter/shAutoloader.js b/src/site/resources/syntaxhighlighter/shAutoloader.js deleted file mode 100644 index 4e29bddecbb..00000000000 --- a/src/site/resources/syntaxhighlighter/shAutoloader.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(2(){1 h=5;h.I=2(){2 n(c,a){4(1 d=0;d|<|≥|>=|≤|<=|\*|\+|-|\/|÷|\^)/g, - css: 'color2' }, - - { regex: /\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, - css: 'keyword' }, - - { regex: /\b\d+(st|nd|rd|th)\b/g, // ordinals - css: 'keyword' }, - - { regex: /\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g, - css: 'color3' }, - - { regex: /\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, - css: 'color3' }, - - { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['applescript']; - - SyntaxHighlighter.brushes.AppleScript = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushBash.js b/src/site/resources/syntaxhighlighter/shBrushBash.js deleted file mode 100644 index 8c296969ff4..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushBash.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'if fi then elif else for do done until while break continue case function return in eq ne ge le'; - var commands = 'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' + - 'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' + - 'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' + - 'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' + - 'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' + - 'import install join kill less let ln local locate logname logout look lpc lpr lprint ' + - 'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' + - 'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' + - 'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' + - 'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' + - 'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' + - 'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' + - 'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' + - 'vi watch wc whereis which who whoami Wget xargs yes' - ; - - this.regexList = [ - { regex: /^#!.*$/gm, css: 'preprocessor bold' }, - { regex: /\/[\w-\/]+/gm, css: 'plain' }, - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(this.getKeywords(commands), 'gm'), css: 'functions' } // commands - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['bash', 'shell']; - - SyntaxHighlighter.brushes.Bash = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushCSharp.js b/src/site/resources/syntaxhighlighter/shBrushCSharp.js deleted file mode 100644 index 079214efe11..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushCSharp.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abstract as base bool break byte case catch char checked class const ' + - 'continue decimal default delegate do double else enum event explicit ' + - 'extern false finally fixed float for foreach get goto if implicit in int ' + - 'interface internal is lock long namespace new null object operator out ' + - 'override params private protected public readonly ref return sbyte sealed set ' + - 'short sizeof stackalloc static string struct switch this throw true try ' + - 'typeof uint ulong unchecked unsafe ushort using virtual void while'; - - function fixComments(match, regexInfo) - { - var css = (match[0].indexOf("///") == 0) - ? 'color1' - : 'comments' - ; - - return [new SyntaxHighlighter.Match(match[0], match.index, css)]; - } - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, func : fixComments }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: /@"(?:[^"]|"")*"/g, css: 'string' }, // @-quoted strings - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /^\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // c# keyword - { regex: /\bpartial(?=\s+(?:class|interface|struct)\b)/g, css: 'keyword' }, // contextual keyword: 'partial' - { regex: /\byield(?=\s+(?:return|break)\b)/g, css: 'keyword' } // contextual keyword: 'yield' - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['c#', 'c-sharp', 'csharp']; - - SyntaxHighlighter.brushes.CSharp = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); - diff --git a/src/site/resources/syntaxhighlighter/shBrushColdFusion.js b/src/site/resources/syntaxhighlighter/shBrushColdFusion.js deleted file mode 100644 index 627dbb9b76e..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushColdFusion.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Jen - // http://www.jensbits.com/2009/05/14/coldfusion-brush-for-syntaxhighlighter-plus - - var funcs = 'Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader AjaxLink AjaxOnLoad ArrayAppend ArrayAvg ArrayClear ArrayDeleteAt ' + - 'ArrayInsertAt ArrayIsDefined ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArraySet ArraySort ArraySum ArraySwap ArrayToList ' + - 'Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet BitNot BitOr BitSHLN BitSHRN BitXor ' + - 'Ceiling CharsetDecode CharsetEncode Chr CJustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject ' + - 'CreateODBCDate CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID DateAdd DateCompare DateConvert ' + - 'DateDiff DateFormat DatePart Day DayOfWeek DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue ' + - 'Decrypt DecryptBinary DeleteClientVariable DeserializeJSON DirectoryExists DollarFormat DotNetToCFType Duplicate Encrypt ' + - 'EncryptBinary Evaluate Exp ExpandPath FileClose FileCopy FileDelete FileExists FileIsEOF FileMove FileOpen FileRead ' + - 'FileReadBinary FileReadLine FileSetAccessMode FileSetAttribute FileSetLastModified FileWrite Find FindNoCase FindOneOf ' + - 'FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData GetBaseTagList GetBaseTemplatePath ' + - 'GetClientVariablesList GetComponentMetaData GetContextRoot GetCurrentTemplatePath GetDirectoryFromPath GetEncoding ' + - 'GetException GetFileFromPath GetFileInfo GetFunctionList GetGatewayHelper GetHttpRequestData GetHttpTimeString ' + - 'GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName GetLocalHostIP GetMetaData GetMetricData ' + - 'GetPageContext GetPrinterInfo GetProfileSections GetProfileString GetReadableImageFormats GetSOAPRequest GetSOAPRequestHeader ' + - 'GetSOAPResponse GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount GetTimeZoneInfo GetToken ' + - 'GetUserRoles GetWriteableImageFormats Hash Hour HTMLCodeFormat HTMLEditFormat IIf ImageAddBorder ImageBlur ImageClearRect ' + - 'ImageCopy ImageCrop ImageDrawArc ImageDrawBeveledRect ImageDrawCubicCurve ImageDrawLine ImageDrawLines ImageDrawOval ' + - 'ImageDrawPoint ImageDrawQuadraticCurve ImageDrawRect ImageDrawRoundRect ImageDrawText ImageFlip ImageGetBlob ImageGetBufferedImage ' + - 'ImageGetEXIFTag ImageGetHeight ImageGetIPTCTag ImageGetWidth ImageGrayscale ImageInfo ImageNegative ImageNew ImageOverlay ImagePaste ' + - 'ImageRead ImageReadBase64 ImageResize ImageRotate ImageRotateDrawingAxis ImageScaleToFit ImageSetAntialiasing ImageSetBackgroundColor ' + - 'ImageSetDrawingColor ImageSetDrawingStroke ImageSetDrawingTransparency ImageSharpen ImageShear ImageShearDrawingAxis ImageTranslate ' + - 'ImageTranslateDrawingAxis ImageWrite ImageWriteBase64 ImageXORDrawingMode IncrementValue InputBaseN Insert Int IsArray IsBinary ' + - 'IsBoolean IsCustomFunction IsDate IsDDX IsDebugMode IsDefined IsImage IsImageFile IsInstanceOf IsJSON IsLeapYear IsLocalHost ' + - 'IsNumeric IsNumericDate IsObject IsPDFFile IsPDFObject IsQuery IsSimpleValue IsSOAPRequest IsStruct IsUserInAnyRole IsUserInRole ' + - 'IsUserLoggedIn IsValid IsWDDX IsXML IsXmlAttribute IsXmlDoc IsXmlElem IsXmlNode IsXmlRoot JavaCast JSStringFormat LCase Left Len ' + - 'ListAppend ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase ListFirst ListGetAt ListInsertAt ' + - 'ListLast ListLen ListPrepend ListQualify ListRest ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log ' + - 'Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime ' + - 'LSParseEuroCurrency LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now NumberFormat ParagraphFormat ParseDateTime ' + - 'Pi PrecisionEvaluate PreserveSingleQuotes Quarter QueryAddColumn QueryAddRow QueryConvertForGrid QueryNew QuerySetCell QuotedValueList Rand ' + - 'Randomize RandRange REFind REFindNoCase ReleaseComObject REMatch REMatchNoCase RemoveChars RepeatString Replace ReplaceList ReplaceNoCase ' + - 'REReplace REReplaceNoCase Reverse Right RJustify Round RTrim Second SendGatewayMessage SerializeJSON SetEncoding SetLocale SetProfileString ' + - 'SetVariable Sgn Sin Sleep SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear StructCopy StructCount StructDelete StructFind ' + - 'StructFindKey StructFindValue StructGet StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructKeyList StructNew ' + - 'StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase URLDecode URLEncodedFormat URLSessionFormat Val ' + - 'ValueList VerifyClient Week Wrap Wrap WriteOutput XmlChildPos XmlElemNew XmlFormat XmlGetNodeType XmlNew XmlParse XmlSearch XmlTransform ' + - 'XmlValidate Year YesNoFormat'; - - var keywords = 'cfabort cfajaximport cfajaxproxy cfapplet cfapplication cfargument cfassociate cfbreak cfcache cfcalendar ' + - 'cfcase cfcatch cfchart cfchartdata cfchartseries cfcol cfcollection cfcomponent cfcontent cfcookie cfdbinfo ' + - 'cfdefaultcase cfdirectory cfdiv cfdocument cfdocumentitem cfdocumentsection cfdump cfelse cfelseif cferror ' + - 'cfexchangecalendar cfexchangeconnection cfexchangecontact cfexchangefilter cfexchangemail cfexchangetask ' + - 'cfexecute cfexit cffeed cffile cfflush cfform cfformgroup cfformitem cfftp cffunction cfgrid cfgridcolumn ' + - 'cfgridrow cfgridupdate cfheader cfhtmlhead cfhttp cfhttpparam cfif cfimage cfimport cfinclude cfindex ' + - 'cfinput cfinsert cfinterface cfinvoke cfinvokeargument cflayout cflayoutarea cfldap cflocation cflock cflog ' + - 'cflogin cfloginuser cflogout cfloop cfmail cfmailparam cfmailpart cfmenu cfmenuitem cfmodule cfNTauthenticate ' + - 'cfobject cfobjectcache cfoutput cfparam cfpdf cfpdfform cfpdfformparam cfpdfparam cfpdfsubform cfpod cfpop ' + - 'cfpresentation cfpresentationslide cfpresenter cfprint cfprocessingdirective cfprocparam cfprocresult ' + - 'cfproperty cfquery cfqueryparam cfregistry cfreport cfreportparam cfrethrow cfreturn cfsavecontent cfschedule ' + - 'cfscript cfsearch cfselect cfset cfsetting cfsilent cfslider cfsprydataset cfstoredproc cfswitch cftable ' + - 'cftextarea cfthread cfthrow cftimer cftooltip cftrace cftransaction cftree cftreeitem cftry cfupdate cfwddx ' + - 'cfwindow cfxml cfzip cfzipparam'; - - var operators = 'all and any between cross in join like not null or outer some'; - - this.regexList = [ - { regex: new RegExp('--(.*)$', 'gm'), css: 'comments' }, // one line and multiline comments - { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // single quoted strings - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // functions - { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['coldfusion','cf']; - - SyntaxHighlighter.brushes.ColdFusion = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushCpp.js b/src/site/resources/syntaxhighlighter/shBrushCpp.js deleted file mode 100644 index 9f70d3aed60..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushCpp.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Copyright 2006 Shin, YoungJin - - var datatypes = 'ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR ' + - 'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH ' + - 'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP ' + - 'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY ' + - 'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT ' + - 'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE ' + - 'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF ' + - 'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR ' + - 'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR ' + - 'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT ' + - 'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 ' + - 'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR ' + - 'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 ' + - 'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT ' + - 'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG ' + - 'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM ' + - 'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t ' + - 'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS ' + - 'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t ' + - '__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t ' + - 'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' + - 'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' + - 'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' + - 'va_list wchar_t wctrans_t wctype_t wint_t signed'; - - var keywords = 'break case catch class const __finally __exception __try ' + - 'const_cast continue private public protected __declspec ' + - 'default delete deprecated dllexport dllimport do dynamic_cast ' + - 'else enum explicit extern if for friend goto inline ' + - 'mutable naked namespace new noinline noreturn nothrow ' + - 'register reinterpret_cast return selectany ' + - 'sizeof static static_cast struct switch template this ' + - 'thread throw true false try typedef typeid typename union ' + - 'using uuid virtual void volatile whcar_t while'; - - var functions = 'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' + - 'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' + - 'setlocale acos asin atan atan2 ceil cos cosh exp fabs floor fmod ' + - 'frexp ldexp log log10 modf pow sin sinh sqrt tan tanh jmp_buf ' + - 'longjmp setjmp raise signal sig_atomic_t va_arg va_end va_start ' + - 'clearerr fclose feof ferror fflush fgetc fgetpos fgets fopen ' + - 'fprintf fputc fputs fread freopen fscanf fseek fsetpos ftell ' + - 'fwrite getc getchar gets perror printf putc putchar puts remove ' + - 'rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ' + - 'ungetc vfprintf vprintf vsprintf abort abs atexit atof atoi atol ' + - 'bsearch calloc div exit free getenv labs ldiv malloc mblen mbstowcs ' + - 'mbtowc qsort rand realloc srand strtod strtol strtoul system ' + - 'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' + - 'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' + - 'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' + - 'clock ctime difftime gmtime localtime mktime strftime time'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /^ *#.*/gm, css: 'preprocessor' }, - { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'color1 bold' }, - { regex: new RegExp(this.getKeywords(functions), 'gm'), css: 'functions bold' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword bold' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['cpp', 'c']; - - SyntaxHighlighter.brushes.Cpp = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushCss.js b/src/site/resources/syntaxhighlighter/shBrushCss.js deleted file mode 100644 index 4297a9a6486..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushCss.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function getKeywordsCSS(str) - { - return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; - }; - - function getValuesCSS(str) - { - return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; - }; - - var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + - 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + - 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + - 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + - 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + - 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + - 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + - 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + - 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + - 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + - 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + - 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + - 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + - 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; - - var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ - 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ - 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+ - 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ - 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ - 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ - 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ - 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ - 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ - 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ - 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ - 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ - 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ - 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; - - var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors - { regex: /(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)/g, css: 'value' }, // sizes - { regex: /!important/g, css: 'color3' }, // !important - { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values - { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts - ]; - - this.forHtmlScript({ - left: /(<|<)\s*style.*?(>|>)/gi, - right: /(<|<)\/\s*style\s*(>|>)/gi - }); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['css']; - - SyntaxHighlighter.brushes.CSS = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushDelphi.js b/src/site/resources/syntaxhighlighter/shBrushDelphi.js deleted file mode 100644 index e1060d44688..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushDelphi.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abs addr and ansichar ansistring array as asm begin boolean byte cardinal ' + - 'case char class comp const constructor currency destructor div do double ' + - 'downto else end except exports extended false file finalization finally ' + - 'for function goto if implementation in inherited int64 initialization ' + - 'integer interface is label library longint longword mod nil not object ' + - 'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended ' + - 'pint64 pointer private procedure program property pshortstring pstring ' + - 'pvariant pwidechar pwidestring protected public published raise real real48 ' + - 'record repeat set shl shortint shortstring shr single smallint string then ' + - 'threadvar to true try type unit until uses val var varirnt while widechar ' + - 'widestring with word write writeln xor'; - - this.regexList = [ - { regex: /\(\*[\s\S]*?\*\)/gm, css: 'comments' }, // multiline comments (* *) - { regex: /{(?!\$)[\s\S]*?}/gm, css: 'comments' }, // multiline comments { } - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /\{\$[a-zA-Z]+ .+\}/g, css: 'color1' }, // compiler Directives and Region tags - { regex: /\b[\d\.]+\b/g, css: 'value' }, // numbers 12345 - { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // numbers $F5D3 - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['delphi', 'pascal', 'pas']; - - SyntaxHighlighter.brushes.Delphi = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushDiff.js b/src/site/resources/syntaxhighlighter/shBrushDiff.js deleted file mode 100644 index e9b14fc580a..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushDiff.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - this.regexList = [ - { regex: /^\+\+\+.*$/gm, css: 'color2' }, - { regex: /^\-\-\-.*$/gm, css: 'color2' }, - { regex: /^\s.*$/gm, css: 'color1' }, - { regex: /^@@.*@@$/gm, css: 'variable' }, - { regex: /^\+[^\+]{1}.*$/gm, css: 'string' }, - { regex: /^\-[^\-]{1}.*$/gm, css: 'comments' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['diff', 'patch']; - - SyntaxHighlighter.brushes.Diff = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushErlang.js b/src/site/resources/syntaxhighlighter/shBrushErlang.js deleted file mode 100644 index 6ba7d9da871..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushErlang.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Jean-Lou Dupont - // http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html - - // According to: http://erlang.org/doc/reference_manual/introduction.html#1.5 - var keywords = 'after and andalso band begin bnot bor bsl bsr bxor '+ - 'case catch cond div end fun if let not of or orelse '+ - 'query receive rem try when xor'+ - // additional - ' module export import define'; - - this.regexList = [ - { regex: new RegExp("[A-Z][A-Za-z0-9_]+", 'g'), css: 'constants' }, - { regex: new RegExp("\\%.+", 'gm'), css: 'comments' }, - { regex: new RegExp("\\?[A-Za-z0-9_]+", 'g'), css: 'preprocessor' }, - { regex: new RegExp("[a-z0-9_]+:[a-z0-9_]+", 'g'), css: 'functions' }, - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['erl', 'erlang']; - - SyntaxHighlighter.brushes.Erland = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushGroovy.js b/src/site/resources/syntaxhighlighter/shBrushGroovy.js deleted file mode 100644 index 6ec5c18521a..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushGroovy.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Andres Almiray - // http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter - - var keywords = 'as assert break case catch class continue def default do else extends finally ' + - 'if in implements import instanceof interface new package property return switch ' + - 'throw throws try while public protected private static'; - var types = 'void boolean byte char short int long float double'; - var constants = 'null'; - var methods = 'allProperties count get size '+ - 'collect each eachProperty eachPropertyName eachWithIndex find findAll ' + - 'findIndexOf grep inject max min reverseEach sort ' + - 'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' + - 'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' + - 'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' + - 'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' + - 'transformChar transformLine withOutputStream withPrintWriter withStream ' + - 'withStreams withWriter withWriterAppend write writeLine '+ - 'dump inspect invokeMethod print println step times upto use waitForOrKill '+ - 'getText'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /""".*"""/g, css: 'string' }, // GStrings - { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'value' }, // numbers - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // goovy keyword - { regex: new RegExp(this.getKeywords(types), 'gm'), css: 'color1' }, // goovy/java type - { regex: new RegExp(this.getKeywords(constants), 'gm'), css: 'constants' }, // constants - { regex: new RegExp(this.getKeywords(methods), 'gm'), css: 'functions' } // methods - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['groovy']; - - SyntaxHighlighter.brushes.Groovy = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushJScript.js b/src/site/resources/syntaxhighlighter/shBrushJScript.js deleted file mode 100644 index ff98daba16e..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushJScript.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'break case catch continue ' + - 'default delete do else false ' + - 'for function if in instanceof ' + - 'new null return super switch ' + - 'this throw true try typeof var while with' - ; - - var r = SyntaxHighlighter.regexLib; - - this.regexList = [ - { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings - { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings - { regex: r.singleLineCComments, css: 'comments' }, // one line comments - { regex: r.multiLineCComments, css: 'comments' }, // multiline comments - { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords - ]; - - this.forHtmlScript(r.scriptScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['js', 'jscript', 'javascript']; - - SyntaxHighlighter.brushes.JScript = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushJava.js b/src/site/resources/syntaxhighlighter/shBrushJava.js deleted file mode 100644 index d692fd63828..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushJava.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'abstract assert boolean break byte case catch char class const ' + - 'continue default do double else enum extends ' + - 'false final finally float for goto if implements import ' + - 'instanceof int interface long native new null ' + - 'package private protected public return ' + - 'short static strictfp super switch synchronized this throw throws true ' + - 'transient try void volatile while'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: /\/\*([^\*][\s\S]*)?\*\//gm, css: 'comments' }, // multiline comments - { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm, css: 'preprocessor' }, // documentation comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi, css: 'value' }, // numbers - { regex: /(?!\@interface\b)\@[\$\w]+\b/g, css: 'color1' }, // annotation @anno - { regex: /\@interface\b/g, css: 'color2' }, // @interface keyword - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // java keyword - ]; - - this.forHtmlScript({ - left : /(<|<)%[@!=]?/g, - right : /%(>|>)/g - }); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['java']; - - SyntaxHighlighter.brushes.Java = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushJavaFX.js b/src/site/resources/syntaxhighlighter/shBrushJavaFX.js deleted file mode 100644 index 1a150a6ad33..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushJavaFX.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Patrick Webster - // http://patrickwebster.blogspot.com/2009/04/javafx-brush-for-syntaxhighlighter.html - var datatypes = 'Boolean Byte Character Double Duration ' - + 'Float Integer Long Number Short String Void' - ; - - var keywords = 'abstract after and as assert at before bind bound break catch class ' - + 'continue def delete else exclusive extends false finally first for from ' - + 'function if import in indexof init insert instanceof into inverse last ' - + 'lazy mixin mod nativearray new not null on or override package postinit ' - + 'protected public public-init public-read replace return reverse sizeof ' - + 'step super then this throw true try tween typeof var where while with ' - + 'attribute let private readonly static trigger' - ; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: /(-?\.?)(\b(\d*\.?\d+|\d+\.?\d*)(e[+-]?\d+)?|0x[a-f\d]+)\b\.?/gi, css: 'color2' }, // numbers - { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'variable' }, // datatypes - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['jfx', 'javafx']; - - SyntaxHighlighter.brushes.JavaFX = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushPerl.js b/src/site/resources/syntaxhighlighter/shBrushPerl.js deleted file mode 100644 index d94a2e0ec52..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushPerl.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by David Simmons-Duffin and Marty Kube - - var funcs = - 'abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr ' + - 'chroot close closedir connect cos crypt defined delete each endgrent ' + - 'endhostent endnetent endprotoent endpwent endservent eof exec exists ' + - 'exp fcntl fileno flock fork format formline getc getgrent getgrgid ' + - 'getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr ' + - 'getnetbyname getnetent getpeername getpgrp getppid getpriority ' + - 'getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid ' + - 'getservbyname getservbyport getservent getsockname getsockopt glob ' + - 'gmtime grep hex index int ioctl join keys kill lc lcfirst length link ' + - 'listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd ' + - 'oct open opendir ord pack pipe pop pos print printf prototype push ' + - 'quotemeta rand read readdir readline readlink readpipe recv rename ' + - 'reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl ' + - 'semget semop send setgrent sethostent setnetent setpgrp setpriority ' + - 'setprotoent setpwent setservent setsockopt shift shmctl shmget shmread ' + - 'shmwrite shutdown sin sleep socket socketpair sort splice split sprintf ' + - 'sqrt srand stat study substr symlink syscall sysopen sysread sysseek ' + - 'system syswrite tell telldir time times tr truncate uc ucfirst umask ' + - 'undef unlink unpack unshift utime values vec wait waitpid warn write'; - - var keywords = - 'bless caller continue dbmclose dbmopen die do dump else elsif eval exit ' + - 'for foreach goto if import last local my next no our package redo ref ' + - 'require return sub tie tied unless untie until use wantarray while'; - - this.regexList = [ - { regex: new RegExp('#[^!].*$', 'gm'), css: 'comments' }, - { regex: new RegExp('^\\s*#!.*$', 'gm'), css: 'preprocessor' }, // shebang - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, - { regex: new RegExp('(\\$|@|%)\\w+', 'g'), css: 'variable' }, - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['perl', 'Perl', 'pl']; - - SyntaxHighlighter.brushes.Perl = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushPhp.js b/src/site/resources/syntaxhighlighter/shBrushPhp.js deleted file mode 100644 index 95e6e4325bb..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushPhp.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var funcs = 'abs acos acosh addcslashes addslashes ' + - 'array_change_key_case array_chunk array_combine array_count_values array_diff '+ - 'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+ - 'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+ - 'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+ - 'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+ - 'array_push array_rand array_reduce array_reverse array_search array_shift '+ - 'array_slice array_splice array_sum array_udiff array_udiff_assoc '+ - 'array_udiff_uassoc array_uintersect array_uintersect_assoc '+ - 'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+ - 'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+ - 'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+ - 'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+ - 'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+ - 'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+ - 'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+ - 'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+ - 'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+ - 'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+ - 'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+ - 'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+ - 'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+ - 'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+ - 'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+ - 'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+ - 'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+ - 'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+ - 'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+ - 'parse_ini_file parse_str parse_url passthru pathinfo print readlink realpath rewind rewinddir rmdir '+ - 'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+ - 'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+ - 'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+ - 'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+ - 'strtoupper strtr strval substr substr_compare'; - - var keywords = 'abstract and array as break case catch cfunction class clone const continue declare default die do ' + - 'else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach ' + - 'function include include_once global goto if implements interface instanceof namespace new ' + - 'old_function or private protected public return require require_once static switch ' + - 'throw try use var while xor '; - - var constants = '__FILE__ __LINE__ __METHOD__ __FUNCTION__ __CLASS__'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\$\w+/g, css: 'variable' }, // variables - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, // common functions - { regex: new RegExp(this.getKeywords(constants), 'gmi'), css: 'constants' }, // constants - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keyword - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['php']; - - SyntaxHighlighter.brushes.Php = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushPlain.js b/src/site/resources/syntaxhighlighter/shBrushPlain.js deleted file mode 100644 index 9f7d9e90c32..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushPlain.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['text', 'plain']; - - SyntaxHighlighter.brushes.Plain = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushPowerShell.js b/src/site/resources/syntaxhighlighter/shBrushPowerShell.js deleted file mode 100644 index 0be17529689..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushPowerShell.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributes by B.v.Zanten, Getronics - // http://confluence.atlassian.com/display/CONFEXT/New+Code+Macro - - var keywords = 'Add-Content Add-History Add-Member Add-PSSnapin Clear(-Content)? Clear-Item ' + - 'Clear-ItemProperty Clear-Variable Compare-Object ConvertFrom-SecureString Convert-Path ' + - 'ConvertTo-Html ConvertTo-SecureString Copy(-Item)? Copy-ItemProperty Export-Alias ' + - 'Export-Clixml Export-Console Export-Csv ForEach(-Object)? Format-Custom Format-List ' + - 'Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command ' + - 'Get-Content Get-Credential Get-Culture Get-Date Get-EventLog Get-ExecutionPolicy ' + - 'Get-Help Get-History Get-Host Get-Item Get-ItemProperty Get-Location Get-Member ' + - 'Get-PfxCertificate Get-Process Get-PSDrive Get-PSProvider Get-PSSnapin Get-Service ' + - 'Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object ' + - 'Import-Alias Import-Clixml Import-Csv Invoke-Expression Invoke-History Invoke-Item ' + - 'Join-Path Measure-Command Measure-Object Move(-Item)? Move-ItemProperty New-Alias ' + - 'New-Item New-ItemProperty New-Object New-PSDrive New-Service New-TimeSpan ' + - 'New-Variable Out-Default Out-File Out-Host Out-Null Out-Printer Out-String Pop-Location ' + - 'Push-Location Read-Host Remove-Item Remove-ItemProperty Remove-PSDrive Remove-PSSnapin ' + - 'Remove-Variable Rename-Item Rename-ItemProperty Resolve-Path Restart-Service Resume-Service ' + - 'Select-Object Select-String Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content ' + - 'Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug ' + - 'Set-Service Set-TraceSource Set(-Variable)? Sort-Object Split-Path Start-Service ' + - 'Start-Sleep Start-Transcript Stop-Process Stop-Service Stop-Transcript Suspend-Service ' + - 'Tee-Object Test-Path Trace-Command Update-FormatData Update-TypeData Where(-Object)? ' + - 'Write-Debug Write-Error Write(-Host)? Write-Output Write-Progress Write-Verbose Write-Warning'; - var alias = 'ac asnp clc cli clp clv cpi cpp cvpa diff epal epcsv fc fl ' + - 'ft fw gal gc gci gcm gdr ghy gi gl gm gp gps group gsv ' + - 'gsnp gu gv gwmi iex ihy ii ipal ipcsv mi mp nal ndr ni nv oh rdr ' + - 'ri rni rnp rp rsnp rv rvpa sal sasv sc select si sl sleep sort sp ' + - 'spps spsv sv tee cat cd cp h history kill lp ls ' + - 'mount mv popd ps pushd pwd r rm rmdir echo cls chdir del dir ' + - 'erase rd ren type % \\?'; - - this.regexList = [ - { regex: /#.*$/gm, css: 'comments' }, // one line comments - { regex: /\$[a-zA-Z0-9]+\b/g, css: 'value' }, // variables $Computer1 - { regex: /\-[a-zA-Z]+\b/g, css: 'keyword' }, // Operators -not -and -eq - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(alias), 'gmi'), css: 'keyword' } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['powershell', 'ps']; - - SyntaxHighlighter.brushes.PowerShell = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushPython.js b/src/site/resources/syntaxhighlighter/shBrushPython.js deleted file mode 100644 index ce77462975f..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushPython.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Gheorghe Milas and Ahmad Sherif - - var keywords = 'and assert break class continue def del elif else ' + - 'except exec finally for from global if import in is ' + - 'lambda not or pass print raise return try yield while'; - - var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' + - 'chr classmethod cmp coerce compile complex delattr dict dir ' + - 'divmod enumerate eval execfile file filter float format frozenset ' + - 'getattr globals hasattr hash help hex id input int intern ' + - 'isinstance issubclass iter len list locals long map max min next ' + - 'object oct open ord pow print property range raw_input reduce ' + - 'reload repr reversed round set setattr slice sorted staticmethod ' + - 'str sum super tuple type type unichr unicode vars xrange zip'; - - var special = 'None True False self cls class_'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, - { regex: /^\s*@\w+/gm, css: 'decorator' }, - { regex: /(['\"]{3})([^\1])*?\1/gm, css: 'comments' }, - { regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, css: 'string' }, - { regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, css: 'string' }, - { regex: /\+|\-|\*|\/|\%|=|==/gm, css: 'keyword' }, - { regex: /\b\d+\.?\w*/g, css: 'value' }, - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, - { regex: new RegExp(this.getKeywords(special), 'gm'), css: 'color1' } - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['py', 'python']; - - SyntaxHighlighter.brushes.Python = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushRuby.js b/src/site/resources/syntaxhighlighter/shBrushRuby.js deleted file mode 100644 index ff82130a7af..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushRuby.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Erik Peterson. - - var keywords = 'alias and BEGIN begin break case class def define_method defined do each else elsif ' + - 'END end ensure false for if in module new next nil not or raise redo rescue retry return ' + - 'self super then throw true undef unless until when while yield'; - - var builtins = 'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' + - 'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' + - 'ThreadGroup Thread Time TrueClass'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\b[A-Z0-9_]+\b/g, css: 'constants' }, // constants - { regex: /:[a-z][A-Za-z0-9_]*/g, css: 'color2' }, // symbols - { regex: /(\$|@@|@)\w+/g, css: 'variable bold' }, // $global, @instance, and @@class variables - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(this.getKeywords(builtins), 'gm'), css: 'color1' } // builtins - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['ruby', 'rails', 'ror', 'rb']; - - SyntaxHighlighter.brushes.Ruby = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushSass.js b/src/site/resources/syntaxhighlighter/shBrushSass.js deleted file mode 100644 index aa04da0996b..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushSass.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function getKeywordsCSS(str) - { - return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; - }; - - function getValuesCSS(str) - { - return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; - }; - - var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + - 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + - 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + - 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + - 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + - 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + - 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + - 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + - 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + - 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + - 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + - 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + - 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + - 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; - - var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ - 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ - 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero digits disc dotted double '+ - 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ - 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ - 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ - 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ - 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ - 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ - 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ - 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ - 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ - 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ - 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; - - var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; - - var statements = '!important !default'; - var preprocessor = '@import @extend @debug @warn @if @for @while @mixin @include'; - - var r = SyntaxHighlighter.regexLib; - - this.regexList = [ - { regex: r.multiLineCComments, css: 'comments' }, // multiline comments - { regex: r.singleLineCComments, css: 'comments' }, // singleline comments - { regex: r.doubleQuotedString, css: 'string' }, // double quoted strings - { regex: r.singleQuotedString, css: 'string' }, // single quoted strings - { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors - { regex: /\b(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)\b/g, css: 'value' }, // sizes - { regex: /\$\w+/g, css: 'variable' }, // variables - { regex: new RegExp(this.getKeywords(statements), 'g'), css: 'color3' }, // statements - { regex: new RegExp(this.getKeywords(preprocessor), 'g'), css: 'preprocessor' }, // preprocessor - { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values - { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['sass', 'scss']; - - SyntaxHighlighter.brushes.Sass = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushScala.js b/src/site/resources/syntaxhighlighter/shBrushScala.js deleted file mode 100644 index 4b0b6f04d29..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushScala.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - // Contributed by Yegor Jbanov and David Bernard. - - var keywords = 'val sealed case def true trait implicit forSome import match object null finally super ' + - 'override try lazy for var catch throw type extends class while with new final yield abstract ' + - 'else do if return protected private this package false'; - - var keyops = '[_:=><%#@]+'; - - this.regexList = [ - { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments - { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // multi-line strings - { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double-quoted string - { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings - { regex: /0x[a-f0-9]+|\d+(\.\d+)?/gi, css: 'value' }, // numbers - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords - { regex: new RegExp(keyops, 'gm'), css: 'keyword' } // scala keyword - ]; - } - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['scala']; - - SyntaxHighlighter.brushes.Scala = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushSql.js b/src/site/resources/syntaxhighlighter/shBrushSql.js deleted file mode 100644 index 5c2cd8806ff..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushSql.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var funcs = 'abs avg case cast coalesce convert count current_timestamp ' + - 'current_user day isnull left lower month nullif replace right ' + - 'session_user space substring sum system_user upper user year'; - - var keywords = 'absolute action add after alter as asc at authorization begin bigint ' + - 'binary bit by cascade char character check checkpoint close collate ' + - 'column commit committed connect connection constraint contains continue ' + - 'create cube current current_date current_time cursor database date ' + - 'deallocate dec decimal declare default delete desc distinct double drop ' + - 'dynamic else end end-exec escape except exec execute false fetch first ' + - 'float for force foreign forward free from full function global goto grant ' + - 'group grouping having hour ignore index inner insensitive insert instead ' + - 'int integer intersect into is isolation key last level load local max min ' + - 'minute modify move name national nchar next no numeric of off on only ' + - 'open option order out output partial password precision prepare primary ' + - 'prior privileges procedure public read real references relative repeatable ' + - 'restrict return returns revoke rollback rollup rows rule schema scroll ' + - 'second section select sequence serializable set size smallint static ' + - 'statistics table temp temporary then time timestamp to top transaction ' + - 'translation trigger true truncate uncommitted union unique update values ' + - 'varchar varying view when where with work'; - - var operators = 'all and any between cross in join like not null or outer some'; - - this.regexList = [ - { regex: /--(.*)$/gm, css: 'comments' }, // one line and multiline comments - { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings - { regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString, css: 'string' }, // single quoted strings - { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'color2' }, // functions - { regex: new RegExp(this.getKeywords(operators), 'gmi'), css: 'color1' }, // operators and such - { regex: new RegExp(this.getKeywords(keywords), 'gmi'), css: 'keyword' } // keyword - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['sql']; - - SyntaxHighlighter.brushes.Sql = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); - diff --git a/src/site/resources/syntaxhighlighter/shBrushVb.js b/src/site/resources/syntaxhighlighter/shBrushVb.js deleted file mode 100644 index be845dc0b30..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushVb.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - var keywords = 'AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto ' + - 'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate ' + - 'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType ' + - 'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each ' + - 'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend ' + - 'Function Get GetType GoSub GoTo Handles If Implements Imports In ' + - 'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module ' + - 'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing ' + - 'NotInheritable NotOverridable Object On Option Optional Or OrElse ' + - 'Overloads Overridable Overrides ParamArray Preserve Private Property ' + - 'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume ' + - 'Return Select Set Shadows Shared Short Single Static Step Stop String ' + - 'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until ' + - 'Variant When While With WithEvents WriteOnly Xor'; - - this.regexList = [ - { regex: /'.*$/gm, css: 'comments' }, // one line comments - { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings - { regex: /^\s*#.*$/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion - { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // vb keyword - ]; - - this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['vb', 'vbnet']; - - SyntaxHighlighter.brushes.Vb = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shBrushXml.js b/src/site/resources/syntaxhighlighter/shBrushXml.js deleted file mode 100644 index 69d9fd0b1f4..00000000000 --- a/src/site/resources/syntaxhighlighter/shBrushXml.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -;(function() -{ - // CommonJS - typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; - - function Brush() - { - function process(match, regexInfo) - { - var constructor = SyntaxHighlighter.Match, - code = match[0], - tag = new XRegExp('(<|<)[\\s\\/\\?]*(?[:\\w-\\.]+)', 'xg').exec(code), - result = [] - ; - - if (match.attributes != null) - { - var attributes, - regex = new XRegExp('(? [\\w:\\-\\.]+)' + - '\\s*=\\s*' + - '(? ".*?"|\'.*?\'|\\w+)', - 'xg'); - - while ((attributes = regex.exec(code)) != null) - { - result.push(new constructor(attributes.name, match.index + attributes.index, 'color1')); - result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string')); - } - } - - if (tag != null) - result.push( - new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword') - ); - - return result; - } - - this.regexList = [ - { regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // - { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // - { regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process } - ]; - }; - - Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['xml', 'xhtml', 'xslt', 'html']; - - SyntaxHighlighter.brushes.Xml = Brush; - - // CommonJS - typeof(exports) != 'undefined' ? exports.Brush = Brush : null; -})(); diff --git a/src/site/resources/syntaxhighlighter/shCore.css b/src/site/resources/syntaxhighlighter/shCore.css deleted file mode 100644 index 34f6864a155..00000000000 --- a/src/site/resources/syntaxhighlighter/shCore.css +++ /dev/null @@ -1,226 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCore.js b/src/site/resources/syntaxhighlighter/shCore.js deleted file mode 100644 index b47b6454721..00000000000 --- a/src/site/resources/syntaxhighlighter/shCore.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('K M;I(M)1S 2U("2a\'t 4k M 4K 2g 3l 4G 4H");(6(){6 r(f,e){I(!M.1R(f))1S 3m("3s 15 4R");K a=f.1w;f=M(f.1m,t(f)+(e||""));I(a)f.1w={1m:a.1m,19:a.19?a.19.1a(0):N};H f}6 t(f){H(f.1J?"g":"")+(f.4s?"i":"")+(f.4p?"m":"")+(f.4v?"x":"")+(f.3n?"y":"")}6 B(f,e,a,b){K c=u.L,d,h,g;v=R;5K{O(;c--;){g=u[c];I(a&g.3r&&(!g.2p||g.2p.W(b))){g.2q.12=e;I((h=g.2q.X(f))&&h.P===e){d={3k:g.2b.W(b,h,a),1C:h};1N}}}}5v(i){1S i}5q{v=11}H d}6 p(f,e,a){I(3b.Z.1i)H f.1i(e,a);O(a=a||0;a-1},3d:6(g){e+=g}};c1&&p(e,"")>-1){a=15(J.1m,n.Q.W(t(J),"g",""));n.Q.W(f.1a(e.P),a,6(){O(K c=1;c<14.L-2;c++)I(14[c]===1d)e[c]=1d})}I(J.1w&&J.1w.19)O(K b=1;be.P&&J.12--}H e};I(!D)15.Z.1A=6(f){(f=n.X.W(J,f))&&J.1J&&!f[0].L&&J.12>f.P&&J.12--;H!!f};1r.Z.1C=6(f){M.1R(f)||(f=15(f));I(f.1J){K e=n.1C.1p(J,14);f.12=0;H e}H f.X(J)};1r.Z.Q=6(f,e){K a=M.1R(f),b,c;I(a&&1j e.58()==="3f"&&e.1i("${")===-1&&y)H n.Q.1p(J,14);I(a){I(f.1w)b=f.1w.19}Y f+="";I(1j e==="6")c=n.Q.W(J,f,6(){I(b){14[0]=1f 1r(14[0]);O(K d=0;dd.L-3;){i=1r.Z.1a.W(g,-1)+i;g=1Q.3i(g/10)}H(g?d[g]||"":"$")+i}Y{g=+i;I(g<=d.L-3)H d[g];g=b?p(b,i):-1;H g>-1?d[g+1]:h}})})}I(a&&f.1J)f.12=0;H c};1r.Z.1e=6(f,e){I(!M.1R(f))H n.1e.1p(J,14);K a=J+"",b=[],c=0,d,h;I(e===1d||+e<0)e=5D;Y{e=1Q.3i(+e);I(!e)H[]}O(f=M.3c(f);d=f.X(a);){I(f.12>c){b.U(a.1a(c,d.P));d.L>1&&d.P=e)1N}f.12===d.P&&f.12++}I(c===a.L){I(!n.1A.W(f,"")||h)b.U("")}Y b.U(a.1a(c));H b.L>e?b.1a(0,e):b};M.1h(/\\(\\?#[^)]*\\)/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"});M.1h(/\\((?!\\?)/,6(){J.19.U(N);H"("});M.1h(/\\(\\?<([$\\w]+)>/,6(f){J.19.U(f[1]);J.2N=R;H"("});M.1h(/\\\\k<([\\w$]+)>/,6(f){K e=p(J.19,f[1]);H e>-1?"\\\\"+(e+1)+(3R(f.2S.3a(f.P+f[0].L))?"":"(?:)"):f[0]});M.1h(/\\[\\^?]/,6(f){H f[0]==="[]"?"\\\\b\\\\B":"[\\\\s\\\\S]"});M.1h(/^\\(\\?([5A]+)\\)/,6(f){J.3d(f[1]);H""});M.1h(/(?:\\s+|#.*)+/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"},M.1B,6(){H J.2K("x")});M.1h(/\\./,6(){H"[\\\\s\\\\S]"},M.1B,6(){H J.2K("s")})})();1j 2e!="1d"&&(2e.M=M);K 1v=6(){6 r(a,b){a.1l.1i(b)!=-1||(a.1l+=" "+b)}6 t(a){H a.1i("3e")==0?a:"3e"+a}6 B(a){H e.1Y.2A[t(a)]}6 p(a,b,c){I(a==N)H N;K d=c!=R?a.3G:[a.2G],h={"#":"1c",".":"1l"}[b.1o(0,1)]||"3h",g,i;g=h!="3h"?b.1o(1):b.5u();I((a[h]||"").1i(g)!=-1)H a;O(a=0;d&&a\'+c+""});H a}6 n(a,b){a.1e("\\n");O(K c="",d=0;d<50;d++)c+=" ";H a=v(a,6(h){I(h.1i("\\t")==-1)H h;O(K g=0;(g=h.1i("\\t"))!=-1;)h=h.1o(0,g)+c.1o(0,b-g%b)+h.1o(g+1,h.L);H h})}6 x(a){H a.Q(/^\\s+|\\s+$/g,"")}6 D(a,b){I(a.Pb.P)H 1;Y I(a.Lb.L)H 1;H 0}6 y(a,b){6 c(k){H k[0]}O(K d=N,h=[],g=b.2D?b.2D:c;(d=b.1I.X(a))!=N;){K i=g(d,b);I(1j i=="3f")i=[1f e.2L(i,d.P,b.23)];h=h.1O(i)}H h}6 E(a){K b=/(.*)((&1G;|&1y;).*)/;H a.Q(e.3A.3M,6(c){K d="",h=N;I(h=b.X(c)){c=h[1];d=h[2]}H\'\'+c+""+d})}6 z(){O(K a=1E.36("1k"),b=[],c=0;c<1z 4I="1Z://2y.3L.3K/4L/5L"><3J><4N 1Z-4M="5G-5M" 6K="2O/1z; 6J=6I-8" /><1t>6L 1v<3B 1L="25-6M:6Q,6P,6O,6N-6F;6y-2f:#6x;2f:#6w;25-22:6v;2O-3D:3C;">1v3v 3.0.76 (72 73 3x)1Z://3u.2w/1v70 17 6U 71.6T 6X-3x 6Y 6D.6t 61 60 J 1k, 5Z 5R 5V <2R/>5U 5T 5S!\'}},1Y:{2j:N,2A:{}},1U:{},3A:{6n:/\\/\\*[\\s\\S]*?\\*\\//2c,6m:/\\/\\/.*$/2c,6l:/#.*$/2c,6k:/"([^\\\\"\\n]|\\\\.)*"/g,6o:/\'([^\\\\\'\\n]|\\\\.)*\'/g,6p:1f M(\'"([^\\\\\\\\"]|\\\\\\\\.)*"\',"3z"),6s:1f M("\'([^\\\\\\\\\']|\\\\\\\\.)*\'","3z"),6q:/(&1y;|<)!--[\\s\\S]*?--(&1G;|>)/2c,3M:/\\w+:\\/\\/[\\w-.\\/?%&=:@;]*/g,6a:{18:/(&1y;|<)\\?=?/g,1b:/\\?(&1G;|>)/g},69:{18:/(&1y;|<)%=?/g,1b:/%(&1G;|>)/g},6d:{18:/(&1y;|<)\\s*1k.*?(&1G;|>)/2T,1b:/(&1y;|<)\\/\\s*1k\\s*(&1G;|>)/2T}},16:{1H:6(a){6 b(i,k){H e.16.2o(i,k,e.13.1x[k])}O(K c=\'\',d=e.16.2x,h=d.2X,g=0;g";H c},2o:6(a,b,c){H\'<2W>\'+c+""},2b:6(a){K b=a.1F,c=b.1l||"";b=B(p(b,".20",R).1c);K d=6(h){H(h=15(h+"6f(\\\\w+)").X(c))?h[1]:N}("6g");b&&d&&e.16.2x[d].2B(b);a.3N()},2x:{2X:["21","2P"],21:{1H:6(a){I(a.V("2l")!=R)H"";K b=a.V("1t");H e.16.2o(a,"21",b?b:e.13.1x.21)},2B:6(a){a=1E.6j(t(a.1c));a.1l=a.1l.Q("47","")}},2P:{2B:6(){K a="68=0";a+=", 18="+(31.30-33)/2+", 32="+(31.2Z-2Y)/2+", 30=33, 2Z=2Y";a=a.Q(/^,/,"");a=1P.6Z("","38",a);a.2C();K b=a.1E;b.6W(e.13.1x.37);b.6V();a.2C()}}}},35:6(a,b){K c;I(b)c=[b];Y{c=1E.36(e.13.34);O(K d=[],h=0;h(.*?))\\\\]$"),s=1f M("(?<27>[\\\\w-]+)\\\\s*:\\\\s*(?<1T>[\\\\w-%#]+|\\\\[.*?\\\\]|\\".*?\\"|\'.*?\')\\\\s*;?","g");(j=s.X(k))!=N;){K o=j.1T.Q(/^[\'"]|[\'"]$/g,"");I(o!=N&&m.1A(o)){o=m.X(o);o=o.2V.L>0?o.2V.1e(/\\s*,\\s*/):[]}l[j.27]=o}g={1F:g,1n:C(i,l)};g.1n.1D!=N&&d.U(g)}H d},1M:6(a,b){K c=J.35(a,b),d=N,h=e.13;I(c.L!==0)O(K g=0;g")==o-3){m=m.4h(0,o-3);s=R}l=s?m:l}I((i.1t||"")!="")k.1t=i.1t;k.1D=j;d.2Q(k);b=d.2F(l);I((i.1c||"")!="")b.1c=i.1c;i.2G.74(b,i)}}},2E:6(a){w(1P,"4k",6(){e.1M(a)})}};e.2E=e.2E;e.1M=e.1M;e.2L=6(a,b,c){J.1T=a;J.P=b;J.L=a.L;J.23=c;J.1V=N};e.2L.Z.1q=6(){H J.1T};e.4l=6(a){6 b(j,l){O(K m=0;md)1N;Y I(g.P==c.P&&g.L>c.L)a[b]=N;Y I(g.P>=c.P&&g.P\'+c+""},3Q:6(a,b){K c="",d=a.1e("\\n").L,h=2u(J.V("2i-1s")),g=J.V("2z-1s-2t");I(g==R)g=(h+d-1).1q().L;Y I(3R(g)==R)g=0;O(K i=0;i\'+j+"":"")+i)}H a},4f:6(a){H a?"<4a>"+a+"":""},4b:6(a,b){6 c(l){H(l=l?l.1V||g:g)?l+" ":""}O(K d=0,h="",g=J.V("1D",""),i=0;i|&1y;2R\\s*\\/?&1G;/2T;I(e.13.46==R)b=b.Q(h,"\\n");I(e.13.44==R)b=b.Q(h,"");b=b.1e("\\n");h=/^\\s*/;g=4Q;O(K i=0;i0;i++){K k=b[i];I(x(k).L!=0){k=h.X(k);I(k==N){a=a;1N a}g=1Q.4q(k[0].L,g)}}I(g>0)O(i=0;i\'+(J.V("16")?e.16.1H(J):"")+\'<3Z 5z="0" 5H="0" 5J="0">\'+J.4f(J.V("1t"))+"<3T><3P>"+(1u?\'<2d 1g="1u">\'+J.3Q(a)+"":"")+\'<2d 1g="17">\'+b+"
        "},2F:6(a){I(a===N)a="";J.17=a;K b=J.3Y("T");b.3X=J.1H(a);J.V("16")&&w(p(b,".16"),"5c",e.16.2b);J.V("3V-17")&&w(p(b,".17"),"56",f);H b},2Q:6(a){J.1c=""+1Q.5d(1Q.5n()*5k).1q();e.1Y.2A[t(J.1c)]=J;J.1n=C(e.2v,a||{});I(J.V("2k")==R)J.1n.16=J.1n.1u=11},5j:6(a){a=a.Q(/^\\s+|\\s+$/g,"").Q(/\\s+/g,"|");H"\\\\b(?:"+a+")\\\\b"},5f:6(a){J.28={18:{1I:a.18,23:"1k"},1b:{1I:a.1b,23:"1k"},17:1f M("(?<18>"+a.18.1m+")(?<17>.*?)(?<1b>"+a.1b.1m+")","5o")}}};H e}();1j 2e!="1d"&&(2e.1v=1v);',62,441,'||||||function|||||||||||||||||||||||||||||||||||||return|if|this|var|length|XRegExp|null|for|index|replace|true||div|push|getParam|call|exec|else|prototype||false|lastIndex|config|arguments|RegExp|toolbar|code|left|captureNames|slice|right|id|undefined|split|new|class|addToken|indexOf|typeof|script|className|source|params|substr|apply|toString|String|line|title|gutter|SyntaxHighlighter|_xregexp|strings|lt|html|test|OUTSIDE_CLASS|match|brush|document|target|gt|getHtml|regex|global|join|style|highlight|break|concat|window|Math|isRegExp|throw|value|brushes|brushName|space|alert|vars|http|syntaxhighlighter|expandSource|size|css|case|font|Fa|name|htmlScript|dA|can|handler|gm|td|exports|color|in|href|first|discoveredBrushes|light|collapse|object|cache|getButtonHtml|trigger|pattern|getLineHtml|nbsp|numbers|parseInt|defaults|com|items|www|pad|highlighters|execute|focus|func|all|getDiv|parentNode|navigator|INSIDE_CLASS|regexList|hasFlag|Match|useScriptTags|hasNamedCapture|text|help|init|br|input|gi|Error|values|span|list|250|height|width|screen|top|500|tagName|findElements|getElementsByTagName|aboutDialog|_blank|appendChild|charAt|Array|copyAsGlobal|setFlag|highlighter_|string|attachEvent|nodeName|floor|backref|output|the|TypeError|sticky|Za|iterate|freezeTokens|scope|type|textarea|alexgorbatchev|version|margin|2010|005896|gs|regexLib|body|center|align|noBrush|require|childNodes|DTD|xhtml1|head|org|w3|url|preventDefault|container|tr|getLineNumbersHtml|isNaN|userAgent|tbody|isLineHighlighted|quick|void|innerHTML|create|table|links|auto|smart|tab|stripBrs|tabs|bloggerMode|collapsed|plain|getCodeLinesHtml|caption|getMatchesHtml|findMatches|figureOutLineNumbers|removeNestedMatches|getTitleHtml|brushNotHtmlScript|substring|createElement|Highlighter|load|HtmlScript|Brush|pre|expand|multiline|min|Can|ignoreCase|find|blur|extended|toLowerCase|aliases|addEventListener|innerText|textContent|wasn|select|createTextNode|removeChild|option|same|frame|xmlns|dtd|twice|1999|equiv|meta|htmlscript|transitional|1E3|expected|PUBLIC|DOCTYPE|on|W3C|XHTML|TR|EN|Transitional||configured|srcElement|Object|after|run|dblclick|matchChain|valueOf|constructor|default|switch|click|round|execAt|forHtmlScript|token|gimy|functions|getKeywords|1E6|escape|within|random|sgi|another|finally|supply|MSIE|ie|toUpperCase|catch|returnValue|definition|event|border|imsx|constructing|one|Infinity|from|when|Content|cellpadding|flags|cellspacing|try|xhtml|Type|spaces|2930402|hosted_button_id|lastIndexOf|donate|active|development|keep|to|xclick|_s|Xml|please|like|you|paypal|cgi|cmd|webscr|bin|highlighted|scrollbars|aspScriptTags|phpScriptTags|sort|max|scriptScriptTags|toolbar_item|_|command|command_|number|getElementById|doubleQuotedString|singleLinePerlComments|singleLineCComments|multiLineCComments|singleQuotedString|multiLineDoubleQuotedString|xmlComments|alt|multiLineSingleQuotedString|If|https|1em|000|fff|background|5em|xx|bottom|75em|Gorbatchev|large|serif|CDATA|continue|utf|charset|content|About|family|sans|Helvetica|Arial|Geneva|3em|nogutter|Copyright|syntax|close|write|2004|Alex|open|JavaScript|highlighter|July|02|replaceChild|offset|83'.split('|'),0,{})) diff --git a/src/site/resources/syntaxhighlighter/shCoreDefault.css b/src/site/resources/syntaxhighlighter/shCoreDefault.css deleted file mode 100644 index 08f9e10e4ea..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreDefault.css +++ /dev/null @@ -1,328 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #e0e0e0 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: black !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #6ce26c !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #6ce26c !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: blue !important; - background: white !important; - border: 1px solid #6ce26c !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: blue !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #6ce26c !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: black !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #008200 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: blue !important; -} -.syntaxhighlighter .keyword { - color: #006699 !important; -} -.syntaxhighlighter .preprocessor { - color: gray !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #006699 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreDjango.css b/src/site/resources/syntaxhighlighter/shCoreDjango.css deleted file mode 100644 index 1db1f70cb0d..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreDjango.css +++ /dev/null @@ -1,331 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0a2b1d !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #233729 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #f8f8f8 !important; -} -.syntaxhighlighter .gutter { - color: #497958 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #41a83e !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #41a83e !important; - color: #0a2b1d !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #96dd3b !important; - background: black !important; - border: 1px solid #41a83e !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #96dd3b !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: white !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #41a83e !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #ffe862 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #f8f8f8 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #336442 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #9df39f !important; -} -.syntaxhighlighter .keyword { - color: #96dd3b !important; -} -.syntaxhighlighter .preprocessor { - color: #91bb9e !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #96dd3b !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #eb939a !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #91bb9e !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #edef7d !important; -} - -.syntaxhighlighter .comments { - font-style: italic !important; -} -.syntaxhighlighter .keyword { - font-weight: bold !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreEclipse.css b/src/site/resources/syntaxhighlighter/shCoreEclipse.css deleted file mode 100644 index a45de9fd8e3..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreEclipse.css +++ /dev/null @@ -1,339 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: white !important; -} -.syntaxhighlighter .line.alt1 { - background-color: white !important; -} -.syntaxhighlighter .line.alt2 { - background-color: white !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #c3defe !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: black !important; -} -.syntaxhighlighter .gutter { - color: #787878 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #d4d0c8 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #d4d0c8 !important; - color: white !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3f5fbf !important; - background: white !important; - border: 1px solid #d4d0c8 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3f5fbf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #aa7700 !important; -} -.syntaxhighlighter .toolbar { - color: #a0a0a0 !important; - background: #d4d0c8 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #a0a0a0 !important; -} -.syntaxhighlighter .toolbar a:hover { - color: red !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: black !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #3f5fbf !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #2a00ff !important; -} -.syntaxhighlighter .keyword { - color: #7f0055 !important; -} -.syntaxhighlighter .preprocessor { - color: #646464 !important; -} -.syntaxhighlighter .variable { - color: #aa7700 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ff1493 !important; -} -.syntaxhighlighter .constants { - color: #0066cc !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #7f0055 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: gray !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: red !important; -} - -.syntaxhighlighter .keyword { - font-weight: bold !important; -} -.syntaxhighlighter .xml .keyword { - color: #3f7f7f !important; - font-weight: normal !important; -} -.syntaxhighlighter .xml .color1, .syntaxhighlighter .xml .color1 a { - color: #7f007f !important; -} -.syntaxhighlighter .xml .string { - font-style: italic !important; - color: #2a00ff !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreEmacs.css b/src/site/resources/syntaxhighlighter/shCoreEmacs.css deleted file mode 100644 index 706c77a0a85..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreEmacs.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: black !important; -} -.syntaxhighlighter .line.alt1 { - background-color: black !important; -} -.syntaxhighlighter .line.alt2 { - background-color: black !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2a3133 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter { - color: #d3d3d3 !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #990000 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #990000 !important; - color: black !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #ebdb8d !important; - background: black !important; - border: 1px solid #990000 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #ebdb8d !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #ff7d27 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #990000 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d3d3d3 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #ff7d27 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #ff9e7b !important; -} -.syntaxhighlighter .keyword { - color: aqua !important; -} -.syntaxhighlighter .preprocessor { - color: #aec4de !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #81cef9 !important; -} -.syntaxhighlighter .constants { - color: #ff9e7b !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: aqua !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ebdb8d !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #ff7d27 !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #aec4de !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreFadeToGrey.css b/src/site/resources/syntaxhighlighter/shCoreFadeToGrey.css deleted file mode 100644 index 6101eba51f0..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreFadeToGrey.css +++ /dev/null @@ -1,328 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #121212 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #2c2c29 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: white !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #3185b9 !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #3185b9 !important; - color: #121212 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #3185b9 !important; - background: black !important; - border: 1px solid #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #3185b9 !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #d01d33 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #3185b9 !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #96daff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: white !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #696854 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #e3e658 !important; -} -.syntaxhighlighter .keyword { - color: #d01d33 !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #898989 !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #aaaaaa !important; -} -.syntaxhighlighter .constants { - color: #96daff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #d01d33 !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #ffc074 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: #4a8cdb !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #96daff !important; -} - -.syntaxhighlighter .functions { - font-weight: bold !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreMDUltra.css b/src/site/resources/syntaxhighlighter/shCoreMDUltra.css deleted file mode 100644 index 2923ce7367b..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreMDUltra.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #222222 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: white !important; -} -.syntaxhighlighter table caption { - color: lime !important; -} -.syntaxhighlighter .gutter { - color: #38566f !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #222222 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: lime !important; -} -.syntaxhighlighter .toolbar { - color: #aaaaff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #aaaaff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #9ccff4 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: lime !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: lime !important; -} -.syntaxhighlighter .keyword { - color: #aaaaff !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: aqua !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ff8000 !important; -} -.syntaxhighlighter .constants { - color: yellow !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #aaaaff !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: red !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: yellow !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreMidnight.css b/src/site/resources/syntaxhighlighter/shCoreMidnight.css deleted file mode 100644 index e3733eed566..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreMidnight.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #0f192a !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #253e5a !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #38566f !important; -} -.syntaxhighlighter table caption { - color: #d1edff !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #0f192a !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #428bdd !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #428bdd !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #1dc116 !important; -} -.syntaxhighlighter .toolbar { - color: #d1edff !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: #d1edff !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #8aa6c1 !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #d1edff !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #428bdd !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #1dc116 !important; -} -.syntaxhighlighter .keyword { - color: #b43d3d !important; -} -.syntaxhighlighter .preprocessor { - color: #8aa6c1 !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #f7e741 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #b43d3d !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #f8bb00 !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/src/site/resources/syntaxhighlighter/shCoreRDark.css b/src/site/resources/syntaxhighlighter/shCoreRDark.css deleted file mode 100644 index d09368384da..00000000000 --- a/src/site/resources/syntaxhighlighter/shCoreRDark.css +++ /dev/null @@ -1,324 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -.syntaxhighlighter a, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter table, -.syntaxhighlighter table td, -.syntaxhighlighter table tr, -.syntaxhighlighter table tbody, -.syntaxhighlighter table thead, -.syntaxhighlighter table caption, -.syntaxhighlighter textarea { - -moz-border-radius: 0 0 0 0 !important; - -webkit-border-radius: 0 0 0 0 !important; - background: none !important; - border: 0 !important; - bottom: auto !important; - float: none !important; - height: auto !important; - left: auto !important; - line-height: 1.1em !important; - margin: 0 !important; - outline: 0 !important; - overflow: visible !important; - padding: 0 !important; - position: static !important; - right: auto !important; - text-align: left !important; - top: auto !important; - vertical-align: baseline !important; - width: auto !important; - box-sizing: content-box !important; - font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; - font-weight: normal !important; - font-style: normal !important; - font-size: 1em !important; - min-height: inherit !important; - min-height: auto !important; -} - -.syntaxhighlighter { - width: 100% !important; - margin: 1em 0 1em 0 !important; - position: relative !important; - overflow: auto !important; - font-size: 1em !important; -} -.syntaxhighlighter.source { - overflow: hidden !important; -} -.syntaxhighlighter .bold { - font-weight: bold !important; -} -.syntaxhighlighter .italic { - font-style: italic !important; -} -.syntaxhighlighter .line { - white-space: pre !important; -} -.syntaxhighlighter table { - width: 100% !important; -} -.syntaxhighlighter table caption { - text-align: left !important; - padding: .5em 0 0.5em 1em !important; -} -.syntaxhighlighter table td.code { - width: 100% !important; -} -.syntaxhighlighter table td.code .container { - position: relative !important; -} -.syntaxhighlighter table td.code .container textarea { - box-sizing: border-box !important; - position: absolute !important; - left: 0 !important; - top: 0 !important; - width: 100% !important; - height: 100% !important; - border: none !important; - background: white !important; - padding-left: 1em !important; - overflow: hidden !important; - white-space: pre !important; -} -.syntaxhighlighter table td.gutter .line { - text-align: right !important; - padding: 0 0.5em 0 1em !important; -} -.syntaxhighlighter table td.code .line { - padding: 0 1em !important; -} -.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { - padding-left: 0em !important; -} -.syntaxhighlighter.show { - display: block !important; -} -.syntaxhighlighter.collapsed table { - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar { - padding: 0.1em 0.8em 0em 0.8em !important; - font-size: 1em !important; - position: static !important; - width: auto !important; - height: auto !important; -} -.syntaxhighlighter.collapsed .toolbar span { - display: inline !important; - margin-right: 1em !important; -} -.syntaxhighlighter.collapsed .toolbar span a { - padding: 0 !important; - display: none !important; -} -.syntaxhighlighter.collapsed .toolbar span a.expandSource { - display: inline !important; -} -.syntaxhighlighter .toolbar { - position: absolute !important; - right: 1px !important; - top: 1px !important; - width: 11px !important; - height: 11px !important; - font-size: 10px !important; - z-index: 10 !important; -} -.syntaxhighlighter .toolbar span.title { - display: inline !important; -} -.syntaxhighlighter .toolbar a { - display: block !important; - text-align: center !important; - text-decoration: none !important; - padding-top: 1px !important; -} -.syntaxhighlighter .toolbar a.expandSource { - display: none !important; -} -.syntaxhighlighter.ie { - font-size: .9em !important; - padding: 1px 0 1px 0 !important; -} -.syntaxhighlighter.ie .toolbar { - line-height: 8px !important; -} -.syntaxhighlighter.ie .toolbar a { - padding-top: 0px !important; -} -.syntaxhighlighter.printing .line.alt1 .content, -.syntaxhighlighter.printing .line.alt2 .content, -.syntaxhighlighter.printing .line.highlighted .number, -.syntaxhighlighter.printing .line.highlighted.alt1 .content, -.syntaxhighlighter.printing .line.highlighted.alt2 .content { - background: none !important; -} -.syntaxhighlighter.printing .line .number { - color: #bbbbbb !important; -} -.syntaxhighlighter.printing .line .content { - color: black !important; -} -.syntaxhighlighter.printing .toolbar { - display: none !important; -} -.syntaxhighlighter.printing a { - text-decoration: none !important; -} -.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { - color: black !important; -} -.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { - color: #008200 !important; -} -.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { - color: blue !important; -} -.syntaxhighlighter.printing .keyword { - color: #006699 !important; - font-weight: bold !important; -} -.syntaxhighlighter.printing .preprocessor { - color: gray !important; -} -.syntaxhighlighter.printing .variable { - color: #aa7700 !important; -} -.syntaxhighlighter.printing .value { - color: #009900 !important; -} -.syntaxhighlighter.printing .functions { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .constants { - color: #0066cc !important; -} -.syntaxhighlighter.printing .script { - font-weight: bold !important; -} -.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { - color: gray !important; -} -.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { - color: #ff1493 !important; -} -.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { - color: red !important; -} -.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { - color: black !important; -} - -.syntaxhighlighter { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt1 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.alt2 { - background-color: #1b2426 !important; -} -.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { - background-color: #323e41 !important; -} -.syntaxhighlighter .line.highlighted.number { - color: #b9bdb6 !important; -} -.syntaxhighlighter table caption { - color: #b9bdb6 !important; -} -.syntaxhighlighter .gutter { - color: #afafaf !important; -} -.syntaxhighlighter .gutter .line { - border-right: 3px solid #435a5f !important; -} -.syntaxhighlighter .gutter .line.highlighted { - background-color: #435a5f !important; - color: #1b2426 !important; -} -.syntaxhighlighter.printing .line .content { - border: none !important; -} -.syntaxhighlighter.collapsed { - overflow: visible !important; -} -.syntaxhighlighter.collapsed .toolbar { - color: #5ba1cf !important; - background: black !important; - border: 1px solid #435a5f !important; -} -.syntaxhighlighter.collapsed .toolbar a { - color: #5ba1cf !important; -} -.syntaxhighlighter.collapsed .toolbar a:hover { - color: #5ce638 !important; -} -.syntaxhighlighter .toolbar { - color: white !important; - background: #435a5f !important; - border: none !important; -} -.syntaxhighlighter .toolbar a { - color: white !important; -} -.syntaxhighlighter .toolbar a:hover { - color: #e0e8ff !important; -} -.syntaxhighlighter .plain, .syntaxhighlighter .plain a { - color: #b9bdb6 !important; -} -.syntaxhighlighter .comments, .syntaxhighlighter .comments a { - color: #878a85 !important; -} -.syntaxhighlighter .string, .syntaxhighlighter .string a { - color: #5ce638 !important; -} -.syntaxhighlighter .keyword { - color: #5ba1cf !important; -} -.syntaxhighlighter .preprocessor { - color: #435a5f !important; -} -.syntaxhighlighter .variable { - color: #ffaa3e !important; -} -.syntaxhighlighter .value { - color: #009900 !important; -} -.syntaxhighlighter .functions { - color: #ffaa3e !important; -} -.syntaxhighlighter .constants { - color: #e0e8ff !important; -} -.syntaxhighlighter .script { - font-weight: bold !important; - color: #5ba1cf !important; - background-color: none !important; -} -.syntaxhighlighter .color1, .syntaxhighlighter .color1 a { - color: #e0e8ff !important; -} -.syntaxhighlighter .color2, .syntaxhighlighter .color2 a { - color: white !important; -} -.syntaxhighlighter .color3, .syntaxhighlighter .color3 a { - color: #ffaa3e !important; -} diff --git a/src/site/resources/syntaxhighlighter/shLegacy.js b/src/site/resources/syntaxhighlighter/shLegacy.js deleted file mode 100644 index 6d9fd4d19f6..00000000000 --- a/src/site/resources/syntaxhighlighter/shLegacy.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * SyntaxHighlighter - * http://alexgorbatchev.com/SyntaxHighlighter - * - * SyntaxHighlighter is donationware. If you are using it, please donate. - * http://alexgorbatchev.com/SyntaxHighlighter/donate.html - * - * @version - * 3.0.83 (July 02 2010) - * - * @copyright - * Copyright (C) 2004-2010 Alex Gorbatchev. - * - * @license - * Dual licensed under the MIT and GPL licenses. - */ -eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('3 u={8:{}};u.8={A:4(c,k,l,m,n,o){4 d(a,b){2 a!=1?a:b}4 f(a){2 a!=1?a.E():1}c=c.I(":");3 g=c[0],e={};t={"r":K};M=1;5=8.5;9(3 j R c)e[c[j]]="r";k=f(d(k,5.C));l=f(d(l,5.D));m=f(d(m,5.s));o=f(d(o,5.Q));n=f(d(n,5["x-y"]));2{P:g,C:d(t[e.O],k),D:d(t[e.N],l),s:d({"r":r}[e.s],m),"x-y":d(4(a,b){9(3 h=T S("^"+b+"\\\\[(?\\\\w+)\\\\]$","U"),i=1,p=0;p - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - io.github.devacfr.maven.skins - reflow-maven-skin - 2.0.0-beta2 - - - - - - - HAPI FHIR - The Open Source FHIR API for Java - false - false - - - CORS - - - false - - - - 2 - - - - - - bootswatch-lumen - sidebar - - - - HAPI FHIR|Test Server - Using HAPI|Contribute - JavaDocs|JXR - Get Help|Maven Reports - - -
        - Hosted on GitHub -
        - Star us and we will love you!
        - - ]]>
        - - HAPI FHIR - ]]> - http://hapifhir.io/ - - %2$s - HAPI FHIR -
        - - - - - -
        - -
        - diff --git a/src/site/xdoc/doc_android.xml.vm b/src/site/xdoc/doc_android.xml.vm deleted file mode 100644 index 1b9096581e3..00000000000 --- a/src/site/xdoc/doc_android.xml.vm +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - Android Support - James Agnew - - - - -
        - -

        - HAPI now has a specially built module for use on Android. Android developers - may use this JAR to take advantage of the FHIR model classes, and the FHIR client (running a FHIR server - on Android is not yet supported. Get in touch if this is something you are interested in working on!) -

        - -

        - As of HAPI FHIR 3.1.0, the hapi-fhir-android module has been streamlined in order - to reduce its footprint. Previous versions of the library included both an XML and a JSON parser - but this has been streamlined to only include JSON support in order to reduce the number of - libraries required in an Android build. -

        - -

        - When using the HAPI FHIR Android client, the client will request only JSON responses - (via the HTTP Accept header) and will not be able to communicate - with FHIR servers that support only XML encoding (few, if any, servers actually exist - with this limitation that we are aware of). -

        - -

        - The Android client also uses the hapi-fhir-client-okhttp module, - which is an HTTP client based on the OkHttp library. This library has proven to be - more powerful and less likely to cause issues on Android than the Apache HttpClient - implementation which is bundled by default. -

        - -

        - Note that the Android JAR is still new and hasn't received as much testing as other - parts of the library. We would greatly appreciate feedback, testing, etc. Also note that - because mobile apps run on less powerful hardware compared to desktop and server applications, - it is all the more important to keep a single instance of the FhirContext - around for good performance, since this object is expensive to create. We are hoping to - improve performance of the creation of this object in a future release. If you are an - Android developer and would like to help with this, please get in touch! -

        - - - -

        - To add the HAPI library via Gradle, you should add the - hapi-fhir-android - library to your Gradle file, as well as a structures library for the appropriate - version of FHIR that you want to support, e.g. - hapi-fhir-structures-dstu3. -

        - - -

        - Uou will also need to manually exclude the Woodstox StAX library from - inclusion, as this library uses namespaces which are prohibited on Android. You should also - exclude -

        - - -

        - To see a sample Gradle file for a working Android project - using HAPI FHIR, see the - Android Integration Test - project. -

        -
        -
        - -
        -

        - On mobile devices, performance problems are particularly noticeable. This - is made worse by the fact that some economy Android devices have much slower performance - than modern desktop computers. See the - Client Configuration Performance - page for some tips on how to improve client performance. -

        -
        - -
        - -

        - The following is intended to be a selection of publicly available open source - Android applications which use HAPI FHIR and might be useful as a reference. -

        -

        - If you know of others, please let us know! -

        - - -
        - - - -
        - diff --git a/src/site/xdoc/doc_cli.xml b/src/site/xdoc/doc_cli.xml deleted file mode 100644 index 7e8b488ff9f..00000000000 --- a/src/site/xdoc/doc_cli.xml +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - Command Line Tool - James Agnew - - - - -
        -

        - hapi-fhir-cli is the HAPI FHIR Command Line tool. It features a number of HAPI's - built-in features as easy to use command line options. -

        - - -

        - You can get the tool by downloading it from our - GitHub Releases page - (look for the archive named hapi-fhir-[version]-cli.tar.bz2 on OSX/Linux or hapi-fhir-[version]-cli.zip on Windows). -

        -

        - When you have downloaded the archive (either ZIP or tar.bz2), expand it into a directory - where you will keep it, and add this directory to your path. -

        -

        - You can now try the tool out by executing the following command: hapi-fhir-cli -

        -

        - This command should show a help screen, as shown in the screeenshot below. -

        - Basic screen shot -
        - - - -

        - hapi-fhir-cli is available as a Homebrew package - for Mac. It can be installed using the following command: -

        - brew install hapi-fhir-cli - -
        - - -

        - Note on Java version support: The HAPI library is designed to - work in Java 6+, but the Command Line Tool required a minimum of Java 8. This - is because the Jetty Web Server that is used within the tool has this requirement. -

        -

        - The tool should work correctly on any system that has Java 8 (or newer) installed. If - it is not working correctly, first try the following command to test if Java is installed:
        - $ java -version -

        -

        - If this command does not produce output similar to the following, you should install/reinstall - Java.
        -

        -

        -

        - If this does not help, please post a question on our - Google Group. -

        -
        - -
        - -
        -

        - The CLI tool can be used to start a local, fully functional FHIR server which you can use - for testing. To start this server, simply issue the command hapi-fhir-cli run-server - as shown in the example below: -

        - Run Server -

        - Once the server has started, you can access the testing webpage by pointing your - browser at http://localhost:8080/. The FHIR - server base URL will be http://localhost:8080/baseDstu2/. -

        -

        - Note that by default this server will not be populated with any resources at all. You can - easily populate it with the FHIR example resources by leaving it running and opening - a second terminal window, then using the hapi-fhir-cli upload-examples command - (see the section below). -

        -

        - The server uses a local Derby database instance for storage. You may want to execute - this command in an empty directory, which you can clear if you want to reset the server. -

        -
        - -
        -

        - The upload-examples command downloads the complete set of FHIR example resources from - the HL7 website, and uploads them to a server of your choice. This can be useful to - populate a server with test data. -

        -

        - To execute this command, uploading test resources to a local CLI server, issue - the following: hapi-fhir-cli upload-examples -t http://localhost:8080/baseDstu2 -

        -

        - Note that this command may take a surprisingly long time to complete because of the - large number of examples. -

        -
        - -
        - -

        - The HAPI FHIR JPA server has a terminology server, and has the ability to - be populated with "external" code systems. These code systems are systems - that contain large numbers of codes, so the codes are not stored directly - inside the resource body. -

        -

        - HAPI has methods for uploading several popular code systems into its tables - using the distribution files produced by the respective code systems. This - is done using the upload-terminology command. The following - examples show how to do this for several popular code systems. -

        -

        - Note that the path and exact filename of the terminology files will likely - need to be adjusted for your local disk structure. -

        -

        - SNOMED CT -

        -
        ./hapi-fhir-cli upload-terminology -d Downloads/SnomedCT_RF2Release_INT_20160131.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://snomed.info/sct
        - -

        LOINC

        -
        ./hapi-fhir-cli upload-terminology -d Downloads/LOINC_2.54_MULTI-AXIAL_HIERARCHY.zip -d Downloads/LOINC_2.54_Text.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://loinc.org
        - -
        - - -
        - -

        - The migrate-database command may be used to Migrate a database - schema when upgrading a - HAPI FHIR JPA project from one version of HAPI - FHIR to another version. -

        - -

        - See Upgrading HAPI FHIR JPA - for information on how to use this command. -

        - -
        - - - -
        diff --git a/src/site/xdoc/doc_converter.xml.vm b/src/site/xdoc/doc_converter.xml.vm deleted file mode 100644 index 173d14cf7cf..00000000000 --- a/src/site/xdoc/doc_converter.xml.vm +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - HL7 FHIR Converter - James Agnew - - - - -
        - - - - -

        - Beginning in HAPI FHIR 2.3, a new experimental feature called - hapi-fhir-converter has been added to the project. This - is an experimental feature so use it with caution! -

        - -

        - This feature allows automated conversion from earlier versions - of the FHIR structures to a later version. -

        - -

        - The following page shows some basic examples. Please get in touch - if you are able to contribute better examples! -

        - - -

        - To use the hapi-fhir-converter module, import the following - dependency into your project pom.xml (or equivalent) -

        - - ca.uhn.hapi.fhir - hapi-fhir-converter - ${project.version} -]]> -
        - - -

        - The following example shows a conversion from a - hapi-fhir-structures-hl7org-dstu2 - structure to a - hapi-fhir-structures-dstu3 structure. -

        - - - - -
        - - -

        - The following example shows a conversion from a - hapi-fhir-structures-dstu2.1 - structure to a - hapi-fhir-structures-dstu3 structure. -

        - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_cors.xml.vm b/src/site/xdoc/doc_cors.xml.vm deleted file mode 100644 index 55e18ad86e0..00000000000 --- a/src/site/xdoc/doc_cors.xml.vm +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - CORS - James Agnew - - - - -
        - -

        - Note that in previous revisions of this document we recommended using the - eBay CORS Filter, but - as of 2016 the eBay filter is no longer being maintained and contains known bugs. - We now recommend against using this filter. -

        - -

        - If you are intending to support JavaScript clients in your server application, - you will generally need to enable Cross Origin Resource Sharing (CORS). There are - a number of ways of supporting this, so two are shown here: -

        -
          -
        • An approach using a HAPI FHIR Server Interceptor (Requires SpringFramework)
        • -
        • An approach using a servlet Filter (Container Specific)
        • -
        - - - -

        - The HAPI FHIR server framework includes an interceptor that can be - used to provide CORS functionality on your server. This mechanism is - nice because it relies purely on Java configuration (no messing around with - web.xml files). HAPI's interceptor is a thin wrapper around Spring Framework's - CorsProcessor class, so it requires Spring to be present on your classpath. -

        - -

        - Spring is generally unlikely to conflict with other libraries so it is usually - safe to add it to your classpath, but it is a fairly large library so if size is - a concern you might opt to use a filter instead. -

        - -

        - The following steps outline how to enable HAPI's CorsInterceptor: -

        - -

        - Add the following dependency to your POM. Note the exclusion of - commons-logging, as we are using SLF4j without commons-logging in - most of our examples. If your application uses commons-logging you don't need - to exclude that dependency. -

        - - org.springframework - spring-web - ${spring_version} - - - commons-logging - commons-logging - - -]]> - -

        - In your server's initialization method, create and register - a CorsInterceptor: -

        - - - - - -
        - - - -

        - The following examples show how to use the Apache Tomcat CorsFilter to enable - CORS support. The filter being used - (org.apache.catalina.filters.CorsFilter) is bundled with Apache - Tomcat so if you are deploying to that server you can use the filter. -

        - -

        - Other containers have similar filters you can use, so consult the documentation - for the given container you are using for more information. (If you have - an example for how to configure a different CORS filter, please send it - our way! Examples are always useful!) -

        - -

        - In your web.xml file (within the WEB-INF directory in your WAR file), - the following filter definition adds the CORS filter, including support - for the X-FHIR-Starter header defined by SMART Platforms. -

        - - - CORS Filter - org.apache.catalina.filters.CorsFilter - - A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials. - cors.allowed.origins - * - - - A comma separated list of HTTP verbs, using which a CORS request can be made. - cors.allowed.methods - GET,POST,PUT,DELETE,OPTIONS - - - A comma separated list of allowed headers when making a non simple CORS request. - cors.allowed.headers - X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization - - - A comma separated list non-standard response headers that will be exposed to XHR2 object. - cors.exposed.headers - Location,Content-Location - - - A flag that suggests if CORS is supported with cookies - cors.support.credentials - true - - - A flag to control logging - cors.logging.enabled - true - - - Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache. - cors.preflight.maxage - 300 - - - - CORS Filter - /* -]]> - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_custom_structures.xml b/src/site/xdoc/doc_custom_structures.xml deleted file mode 100644 index b2bd745f523..00000000000 --- a/src/site/xdoc/doc_custom_structures.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - Custom Structures - - - - -
        - -

        - Typically, when working with FHIR the right way to provide your - own extensions is to work with existing resource types and simply - add your own extensions and/or constrain out fields you don't - need. -

        -

        - This process is described on the - Profiles & Extensions - page. -

        -

        - There are situations however when you might want to create an - entirely custom resource type. This feature should be used - only if there is no other option, since it means you are creating - a resource type that will not be interoperable with other FHIR - implementations. -

        -

        - This is an advanced features and isn't needed for most uses of - HAPI-FHIR. Feel free to skip this page. -

        - - - -

        - The following example shows a custom resource structure - class: -

        - - - - - -
        - - - -

        - The following example shows a custom datatype structure - class: -

        - - - - - -
        - - - -

        - And now let's try the custom structure out: -

        - - - - - -

        - This produces the following output (some spacing has been added for readability): -

        - - - - - - - - - - - - - -]]> - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_dstu2.xml b/src/site/xdoc/doc_dstu2.xml deleted file mode 100644 index da86a725d4f..00000000000 --- a/src/site/xdoc/doc_dstu2.xml +++ /dev/null @@ -1,229 +0,0 @@ - - - - - FHIR DSTU3 Support - James Agnew - - - - -
        - -

        - Although DSTU3 has not yet been balloted and finalized by HL7, - there are several proposed changes which have been incorporated - into the current - Continuous Integration Builds - of the FHIR specification itself and many of these changes are used - as a part of testing scenarios in FHIR Connectathons. -

        -

        - HAPI has support for DSTU3 definitions, based on the snapshot - of the resource definitions available at the time that - a given version of HAPI is released. These structures are found - in the hapi-fhir-structures-dstu3-[version].jar - library, with the associated Schemas, ValueSets, and other - textual resources being found in - hapi-fhir-validation-resources-dstu3-[version].jar. - For information on where to find these libraries, see the - Download page. -

        -

        - Migrating to DSTU3 does require some effort, as the resource - definitions have been migrated to use the "Reference Implementation" - structures. These are the resource definitions supplied by HL7, - meaning that the process to merge these two libraries has now - begun. -

        - - -

        - Since the early days of the FHIR project, there have been two parallel - Java implementations of the FHIR Specification: HAPI and the Reference - Implementation (RI). The two libraries both had separate data models and parsers, - but had little overlap in features other than that. HAPI has a server, - database, and rich fluent client that the RI did not have. The RI had - profile validation, snapshot generation, and a set other of great utilities that - HAPI did not have. -

        -

        - Over the last year, we have been working to bring the two projects - together, in order to reduce duplication of effort and let all - Java users take advantage of the entire set of available tools. -

        -

        - The biggest change to HAPI users coming from this merging is the adoption - of the new RI data structure classes. For users of FHIR DSTU2, we provided - a parallel set of structures so that users could choose which library to - use (hapi-fhir-structures-dstu2 for HAPI structures, - or hapi-fhir-structures-hl7org-dstu2 for RI structures). For - DSTU3 we will be using the RI structures only, so users will need to migrate - to use these. -

        -
        - -

        - The reference implementation (RI) structures have been added as a new - maven dependency library called hapi-fhir-structures-dstu3. - See - the download page for information on the Maven - dependencies for this version of the structures. -

        -

        - A new interface has been added which serves as a master interface - for all resource classes: org.hl7.fhir.instance.model.api.IBaseResource. - All RI resource classes will be in the package org.hl7.fhir.dstu3.model, - as shown below. -

        - Structures - -

        - Datatypes will also be found inthe same package. Unlike HAPI datatype structures, - which all end with "Dt", the RI primitive structure names end with "Type" and the - RI composite structures have no suffix, as shown below. -

        - Structures - -
        - - - -

        - Using these structures is similar to using other structures: -

        - - - - - - -
        - -
        - -
        - -

        - If you have an existing application built using a version of previous - version of HAPI FHIR, there is one change that may affect you. As shown above, - a new interface called IBaseResource has been introduced, and the - IResource interface extends from it. Many methods in the API which - previously returned IResource now return IBaseResource. -

        -

        - For these methods, you may cast the IBaseResource to - IResource if you are using DSTU2 structures, or to - IAnyResource if you are using DSTU3 structures. -

        - -

        - Please post any problems you might encounter trying to upgrade on the - Google Group. Chances are - if it's happening to you, it's happening to others. We're happy to help. -

        - - - -

        - The following is a list of things that have changed in the DSTU3 - structures which you will need to accomodate in your code as you - upgrade: -

        - -

        Package Structure

        -
          -
        • - Structures are all found in the - org.hl7.fhir.dstu3.model package, - instead of the ca.uhn.fhir.model.dstu2.resource, - ca.uhn.fhir.model.dstu2.composite, and - ca.uhn.fhir.model.dstu2.valueset packages. -
        • -
        - -

        Datatypes

        -
          -
        • - Primitive Types are renamed from - [foo]Dt to [foo]Type, - e.g. StringType and DecimalType -
        • -
        • - Composite Types are renamed from - [foo]Dt to [foo], - e.g. CodeableConcept and HumanName -
        • -
        • - ResourceReferenceDt is renamed to - Reference -
        • - Reference#getReference() returns the reference text, where - Reference#getReferenceElement() returns the IdType - representing the reference. -
        • - -
        - -

        Setter Names

        -
          -
        • - Names for some component setters/mutators have changed - in the new structures. In the old structures if the field - type was a primitive (e.g. a string) there would be two - setters: setName(String) and - setName(StringDt). In the new structures, - the setters are called setName(String) and - setNameElement(StringType). This is more - consistent with the way the getters are named. -
        • -
        - -

        Resource Metadata

        -
          -
        • - Resource#getId() returns the string ID (e.g. http://example.com/Patient/1) -
        • -
        • - Resource#getIdElement() returns the IdType previously returned by Resource#getId() -
        • -
        • - Resource metadata (e.g. last update time, tags, etc.) lives in a - Meta object accessed using Resource#getMeta() - instead of using the #getResourceMetadata() hashmap. -
        • Resource#getMeta()#getLastUpdate() returns the resource's last update time
        • -
        • Resource#getMeta()#getTag() returns the resource's tag list
        • -
        • Resource#getMeta()#getProfile() returns the resource's profile list
        • - -
        - -

        Contained Resources

        -
          -
        • Resource#getContained() returns the list of contained resources (previously it returned a useless ContainedDt object which held the list of contained resources)
        • -
        - -

        Enums for ValueSets

        -
          -
        • Enums are named [EnumName] instead of [EnumName]Enum. For example, ConditionVerificationStatusEnum is now called ConditionVerificationStatus
        • -
        - -

        - Resource/Datatype Components -

        -
          -
        • - The Java model classes representing sub-elements within a resource - now have a longer name reflecting the containing element name and - ending in "Component". For example, the Java structure representing - the "Bundle.entry" component was called Entry in the HAPI structures - but is called BundleEntryComponent in the RI structures. -
        • -
        - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_extensions.xml b/src/site/xdoc/doc_extensions.xml deleted file mode 100644 index 7c408dcb650..00000000000 --- a/src/site/xdoc/doc_extensions.xml +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - Profiles and Extensions - James Agnew - - - - -
        - -

        - Note on FHIR Versions: Because of the differences in the way the structures - work between DSTU2 and DSTU3, we have provided two versions of many of the - examples on this page. See the download page - for more information on FHIR versions. -

        - -

        - Extensions are a key part of the FHIR specification, providing a standardized - way of placing additional data in a resource. -

        - -

        - The simplest way to interact with extensions (i.e. to add them to resources you are creating, or - to read them from resources you are consuming) is to treat them as "undeclared extensions". - Undeclared extensions can be added to any of the built in FHIR resource types that come with HAPI-FHIR. -

        - -

        - DSTU2 -

        - - - - - -

        - DSTU3 -

        - - - - - -

        - Undeclared extensions can also be added to datatypes (composite or primitive). -

        - -

        - DSTU2 -

        - - - - - -

        - DSTU3 -

        - - - - - - - -

        - Extensions may also have child extensions as their content, instead - of a datatype. This is done by adding a child undeclared extension to the - parent extension. -

        - -

        - DSTU2 -

        - - - - - -

        - DSTU3 -

        - - - - - -
        - - - -

        - HAPI provides a few ways of accessing extension values in resources - which are received from other sources (i.e. downloaded by a client). -

        - -

        - DSTU2 -

        - - - - - -

        - DSTU3 -

        - - - - - -
        - -
        - -
        - -

        - The most elegant way of adding extensions to a resource is through the - use of custom fields. The following example shows a custom type which - extends the FHIR Patient resource definition through two extensions. -

        - - - - - - -

        - Using this custom type is as simple as instantiating the type - and working with the new fields. -

        - - - - - - -

        - This example produces the following output: -

        - - - - - - - - - - - - - - - - - -]]> - -

        - Parsing messages using your new custom type is equally simple. - These types can also be used as method return types in clients - and servers. -

        - - - - - - - - -

        - If you are using a client and wish to use a specific custom structure, - you may simply use the custom structure as you would a build in - HAPI type. -

        - - - - - -

        - You may also explicitly use custom types in searches and other - operations which return resources. -

        - - - - - -

        - You can also explicitly declare a preferred response resource custom - type. This is useful for some operations that do not otherwise - declare their resource types in the method signature. -

        - - - - - -
        - - - -

        - Sometimes you may not know in advance exactly which - type you will be receiving. For example, there are Patient resources - which conform to several different profiles on a server and you - aren't sure which profile you will get back for a specific read, - you can declare the "primary" type for a given profile. -

        -

        - This is declared at the FhirContext level, and will apply to any - clients created from this context (including clients created before - the default was set). -

        - - - - - -
        - - - -

        - If you are using a client and wish to use a specific custom structure, - you may simply use the custom structure as you would a build in - HAPI type. -

        - - - - - -
        - - - -

        - The following example shows a resource containing a composite - extension. -

        - - - - - - -

        - This could be used to create a resource such as the - following: -

        - - - - - - - - - - -]]> - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_fhirobjects.xml b/src/site/xdoc/doc_fhirobjects.xml deleted file mode 100644 index 10130c0671c..00000000000 --- a/src/site/xdoc/doc_fhirobjects.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - Data Model - James Agnew - - - - - -
        - -

        - Every resource type defined by FHIR has a corresponding - class, which contains a number of getters and setters for - the basic properties of that resource. -

        - -

        - HAPI tries to make populating objects easier, by providing lots of - convenience methods. For example, the Observation resource has an - "issued" property which is of the FHIR "instant" type (a system time with - either seconds or milliseconds precision). There are methods to - use the actual FHIR datatype, but also convenience methods which - use built-in Java types. -

        - - - - - - - - -

        - Most HAPI structures provide getters that automatically create - child objects on access. This means it is simple to navigate - complex structures without needing to worry about instantiating - child objects. -

        - - - - - - -
        - - - -

        - There are many places in the FHIR specification where a "coded" string is - used. This means that a code must be chosen from a list of allowable values. -

        - -

        Closed Valuesets / Codes

        - -

        - The FHIR specification defines a number of "closed" ValueSets, such as - the one used for - - Patient.gender - (note that this field was not a closed ValueSet in DSTU1 but is as of DSTU2). - These valuesets must either be empty, or be populated with a value drawn from - the list of allowable values defined by FHIR. HAPI provides special typesafe - Enums to help in dealing with these fields. -

        - - - - - - -

        Open Valusets / CodeableConcepts

        - -

        - The FHIR specification also defines a number of "open" ValueSets, such as - the one used for - Patient.maritalStatus. - These fields may define a set of allowable codes, but also allow you to - use your own codes instead if none of the given codes matches your needs. This - is called an incomplete binding. - Some fields may even define a set of codes that serve as nothing more than - an example as to the type of codes that would be used there. This is known as - an example binding. -

        - -

        - For these fields, a CodeableConcept datatype is generally used by the - FHIR specification. This datatype allows multiple "codings", which - are a code and codesystem pair, optionally with a display name as well. - The following example shows how to interact with this type. -

        - - - - - - -

        - HAPI also provides typesafe enums to help in working with CodeableConcept - fields. -

        - - - - - - -
        - - - -

        - The FHIR data model is rich enough to meet common use cases, but sometimes - that richness adds complexity. For example, a Patient may have multiple names - (a preferred name, a nickname, etc.) and each of those names may have multiple - last names, multiple prefixes, etc. -

        - -

        - The example below shows populating a name entry for a Patient. Note the - use of the StringDt type, which encapsulates a regular String, but allows for - extensions to be added. -

        - - - - - - -

        - HAPI also provides for simple setters that use Java primitive types - and can be chained, leading to much simpler code. -

        - - - - - - -
        - -
        - -
        - - - -

        - The following example shows how to create an observation resource containing - a numeric datatype. -

        - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_interceptors.xml b/src/site/xdoc/doc_interceptors.xml deleted file mode 100644 index fad0c1bd1a6..00000000000 --- a/src/site/xdoc/doc_interceptors.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - Interceptors - James Agnew - - - - -
        - -

        - HAPI FHIR 3.8.0 introduced a new interceptor framework that is used across the entire - library. In previous versions of HAPI FHIR, a "Server Interceptor" framework existed and a - separate "Client Interceptor" framework existed. These have now been combined into a single - unified (and very powerful) framework. -

        - -

        - Interceptor classes may "hook into" various points in the processing chain in both - the client and the erver. -

        - - - - - - -
        - - - -
        diff --git a/src/site/xdoc/doc_intro.xml b/src/site/xdoc/doc_intro.xml deleted file mode 100644 index 400f61e11e8..00000000000 --- a/src/site/xdoc/doc_intro.xml +++ /dev/null @@ -1,227 +0,0 @@ - - - - - Introduction - James Agnew - - - - - - - -
        - -

        - The HAPI FHIR library is an implementation of the - HL7 FHIR specification - for Java. Explaining what FHIR is would be beyond the scope of this documentation, - so if you have not previously worked with FHIR, the specification is a good place to - start. This is often not actually the case when discussing messaging protocols, but - in this case it is so: The FHIR specification is designed to be readable and - implementable, and is filled with good information. -

        - -

        - Part of the key to why FHIR is a good specification is the fact that its design - is based on the design of other successful APIs (in particular, the FHIR designers - often reference the Highrise API as a key influence in the design of the spec.) -

        - -

        - HAPI FHIR is based on the same principle, but applied to the Java implementation: We - have based the design of this API on the JAXB and JAX-WS APIs, which we consider to be - very well thought-out, and very usable APIs. This does not mean that HAPI-FHIR - actually uses these two APIs however, or that HAPI-FHIR is in any way compliant with - JAXB (JSR222) or JAX-WS - (JSR224), only that we have tried - to emluate the easy-to-use, but flexible design of these specifications. -

        - -
        - -
        - -

        - To get started with HAPI FHIR, first download a copy and add it - to your project. See the Download Page - for instructions. -

        - - - -

        - Before discussing HAPI itself, a quick word about FHIR versions. FHIR - is not yet a finalized "1.0" standard. It is currently in the DSTU phase, - which means that it is changing in subtle and non-subtle ways between releases. - Before trying to use FHIR, you will need to determine which version of FHIR - you want to support in your application. Typically this would be the - latest version, but if you are looking to interact with an application which - already exists, you will probably want to implement the same version implemented - by that application. -

        -

        - See the - note on DSTU2 support - for more information on supporting multiple versions of FHIR. -

        - -
        - - - -

        - HAPI defines model classes for every resource type and datatype defined by the FHIR specification. - For example, here is the Patient - resource specification. If you browse the JavaDoc you will see getters and setters for the - various properties that make up a Patient resource. -

        - -

        - We will come back to how to interact with these objects in a moment, but first - we need to create a - FhirContext. - FhirContext is the starting point to using HAPI, and acts as a factory for most - other parts of the API as well as a runtime cache of information that HAPI needs - to operate. Users of the JAXB API may find this class to be similar in purpose to - the - JAXBContext - class from that API. -

        - -

        - Creating a FhirContext is as simple as instantiating one. A FhirContext instance is - specific to a given version of the FHIR specification, so it is recommended that you - use one of the factory methods indicating the FHIR version you wish to support in your - application, as shown in the following snippet: -

        - - - - - - -
        - - - -

        - This - Parser instance - can then be used to parse messages. Note that you may use the context to - create as many parsers are you want. -

        - -

        - Performance tip: - The FhirContext is an expensive object to create, so you should try to create - it once and keep it around during the life of your application. Parsers, on - the other hand, are very lightweight and do not need to be reused. -

        - - - - - - -
        - - - -

        - The parser can also be used to encode a resource (which you can populate - with your own values) just as easily. -

        - - - - - - - - -

        - This code gives the following output: -

        - - - - - - - - - - - - -]]> - -
        - - - -

        - Much of the HAPI FHIR API is designed using a fluent style, - where method calls can be chained in a natural way. This - leads to tighter and easier-to-read code. -

        - -

        - The following snippet is functionally identical to the - example above: -

        - - - - - - -
        - - - -

        - JSON parsing/encoding is also supported. -

        - - - - - - -

        - This code gives the following output: -

        - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_jpa.xml b/src/site/xdoc/doc_jpa.xml deleted file mode 100644 index 49892a661f2..00000000000 --- a/src/site/xdoc/doc_jpa.xml +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - - JPA Server - James Agnew - - - - -
        - -

        - The HAPI FHIR - RestfulServer - module can be used to create a FHIR server endpoint against an arbitrary - data source, which could be a database of your own design, an existing - clinical system, a set of files, or anything else you come up with. -

        -

        - HAPI also provides a persistence module which can be used to - provide a complete RESTful server implementation, backed by a database of - your choosing. This module uses the - JPA 2.0 - API to store data in a database without depending on any specific database technology. -

        -

        - Important Note: - This implementation uses a fairly simple table design, with a - single table being used to hold resource bodies (which are stored as - CLOBs, optionally GZipped to save space) and a set of tables to hold search indexes, tags, - history details, etc. This design is only one of many possible ways - of designing a FHIR server so it is worth considering whether it - is appropriate for the problem you are trying to solve. -

        - - - -

        - The easiest way to get started with HAPI's JPA server module is - to begin with the example project. There is a complete sample project - found in our GitHub repo here: - - hapi-fhir-jpaserver-example - -

        - -

        - This example is a fully contained FHIR server, supporting all standard operations - (read/create/delete/etc). - It bundles an embedded instance of the Apache Derby Java - database - so that the server can run without depending on any external database, but it can also be - configured to use an installation of Oracle, Postgres, etc. -

        - -

        - To take this project for a spin, check out the sources from GitHib (or download a snapshot), - and then build the project: -

        - - - -

        - You now have two options for starting the server: -

        -
          -
        • - Deploy to Tomcat/JBoss/Websphere/etc: - You will now have a file - in your target directory called hapi-fhir-jpaserver-example.war. - This WAR file can be deployed to any Servlet container, at which point you could - access the server by pointing your browser at a URL similar to the following - (you may need to adjust the - port depending on which port your container is configured to listen on): - - http://localhost:8080/hapi-fhir-jpaserver-example/ - -
        • -
        • - Run with Maven and Embedded Jetty: - To start the server - directly within Maven, you can execute the following command: -
          - $ mvn jetty:run - You can then access the server by pointing your browser at the following URL: - - http://localhost:8080/hapi-fhir-jpaserver-example/ - -
        • -
        -
        -
        - -
        - -

        - The JPA server is configured through a series of configuration files, most - of which are documented inline. -

        - - -
        - -
        - -

        - The Spring confguration contains a definition for a bean called daoConfig, - which will look something like the following: -

        - - -

        - You can use this method to change various configuration settings on the DaoConfig bean - which define the way that the JPA server will behave. - See the - DaoConfig JavaDoc - for information about the available settings. -

        - - - -

        - Clients may sometimes post resources to your server that contain - absolute resource references. For example, consider the following resource: -

        - - - - - - - - - - -]]> - -

        - By default, the server will reject this reference, as only - local references are permitted by the server. This can be changed - however. -

        -

        - If you want the server to recognize that this URL is actually a local - reference (i.e. because the server will be deployed to the base URL - http://example.com/fhir/) you can - configure the server to recognize this URL via the following DaoConfig - setting: -

        - - -

        - On the other hand, if you want the server to be configurable to - allow remote references, you can set this with the confguration below. - Using the setAllowExternalReferences means that - it will be possible to search for references that refer to these - external references. -

        - - -
        - - - -

        - In some cases, you may have references which are Logical References, - which means that they act as an identifier and not necessarily as a literal - web address. -

        -

        - A common use for logical references is in references to conformance - resources, such as ValueSets, StructureDefinitions, etc. For example, - you might refer to the ValueSet - http://hl7.org/fhir/ValueSet/quantity-comparator - from your own resources. In this case, you are not neccesarily telling - the server that this is a real address that it should resolve, but - rather that this is an identifier for a ValueSet where - ValueSet.url - has the given URI/URL. -

        -

        - HAPI can be configured to treat certain URI/URL patterns as - logical by using the DaoConfig#setTreatReferencesAsLogical property - (see - JavaDoc). - For example: -

        -
        -
        -						// Treat specific URL as logical
        -						myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats-and-dogs");
        -
        -						// Treat all references with given prefix as logical
        -						myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/mysystem-vs-*");
        -					
        -
        - -
        - - - -

        - By default, search results will be cached for one minute. This means that - if a client performs a search for Patient?name=smith and gets back - 500 results, if a client performs the same search within 60000 milliseconds the - previously loaded search results will be returned again. This also means that - any new Patient resources named "Smith" within the last minute will not be - reflected in the results. -

        -

        - Under many normal scenarios this is a n acceptable performance tradeoff, - but in some cases it is not. If you want to disable caching, you have two - options: -

        -

        - Globally Disable / Change Caching Timeout -

        -

        - You can change the global cache using the following setting: -

        -
        -
        -						myDaoConfig.setReuseCachedSearchResultsForMillis(null);
        -					
        -
        -

        - Disable Cache at the Request Level -

        -

        - Clients can selectively disable caching for an individual request - using the Cache-Control header: -

        -
        -
        -						Cache-Control: no-cache
        -					
        -
        -

        - Disable Paging at the Request Level -

        -

        - If the client knows that they will only want a small number of results - (for example, a UI containing 20 results is being shown and the client - knows that they will never load the next page of results) the client - may also use the no-store directive along with a HAPI FHIR - extension called max-results in order to specify that - only the given number of results should be fetched. This directive - disabled paging entirely for the request and causes the request to - return immediately when the given number of results is found. This - can cause a noticeable performance improvement in some cases. -

        -
        -
        -						Cache-Control: no-store, max-results=20
        -					
        -
        - -
        - -
        - -
        - - Architecture - -

        - The HAPI JPA Server has the following components: -

        - -
          -
        • - Resource Providers: - A RESTful server Resource Provider is - provided for each resource type in a given release of FHIR. Each resource provider implements - a - @Search - method implementing the complete set of search parameters defined in the FHIR - specification for the given resource type. -
          -
          - The resource providers also extend a superclass which implements all of the - other FHIR methods, such as Read, Create, Delete, etc. -
          -
          - Note that these resource providers are generated as a part of the HAPI build process, - so they are not checked into Git. You can see their source - in the JXR Report, - for example the - - PatientResourceProvider. -
          -
          - The resource providers do not actually implement any of the logic - in searching, updating, etc. They simply receive the incoming HTTP calls (via the RestfulServer) - and pass along the incoming requests to the DAOs. -
          -
          -
        • -
        • - HAPI DAOs: - The DAOs actually implement all of the database business logic relating to - the storage, indexing, and retrieval of FHIR resources, using the underlying JPA - API. -
          -
          -
        • -
        • - Hibernate: - The HAPI JPA Server uses the JPA library, implemented by Hibernate. No Hibernate - specific features are used, so the library should also work with other - providers (e.g. Eclipselink) but it is not tested regularly with them. -
          -
          -
        • -
        • - Database: - The RESTful server uses an embedded Derby database, but can be configured to - talk to - any database supported by - Hibernate. -
        • - -
        - -
        - -
        - -
          -
        • - This page - has information on loading national editions (UK specifically) of SNOMED CT files into - the database. -
        • -
        - -
        - - - - -
        - -

        - HAPI FHIR JPA is a constantly evolving product, with new features being added to each - new version of the library. As a result, it is generally necessary to execute a database - migration as a part of an upgrade to HAPI FHIR. -

        - -

        - When upgrading the JPA server from one version of HAPI FHIR to a newer version, - often there will be changes to the database schema. The - Migrate Database - command can be used to perform a migration from one version to the next. -

        - -

        - Note that this feature was added in HAPI FHIR 3.5.0. It is not able to migrate - from versions prior to HAPI FHIR 3.4.0. - Please make a backup of your - database before running this command! - -

        -

        - The following example shows how to use the migrator utility to migrate between two versions. -

        -
        ./hapi-fhir-cli migrate-database -d DERBY_EMBEDDED -u
        -				"jdbc:derby:directory:target/jpaserver_derby_files;create=true" -n "" -p "" -f V3_4_0 -t V3_5_0
        -			
        - -

        - You may use the following command to get detailed help on the options: -

        -
        ./hapi-fhir-cli help migrate-database
        - -

        - Note the arguments: -

          -
        • - -d [dialect] - - This indicates the database dialect to use. See the detailed help for a list of options -
        • -
        • - -f [version] - - The version to migrate from -
        • -
        • - -t [version] - - The version to migrate to -
        • -
        -

        - - -

        - Note that the Oracle JDBC drivers are not distributed in the Maven Central repository, - so they are not included in HAPI FHIR. In order to use this command with an Oracle database, - you will need to invoke the CLI as follows: -

        -
        java -cp hapi-fhir-cli.jar ca.uhn.fhir.cli.App migrate-database -d ORACLE_12C -u "[url]" -n
        -					"[username]" -p "[password]" -f V3_4_0 -t V3_5_0
        -				
        -
        - - -

        - As of HAPI FHIR 3.5.0 a new mechanism for creating the JPA index tables (HFJ_SPIDX_xxx) - has been implemented. This new mechanism uses hashes in place of large multi-column - indexes. This improves both lookup times as well as required storage space. This change - also paves the way for future ability to provide efficient multi-tenant searches (which - is not yet implemented but is planned as an incremental improvement). -

        -

        - This change is not a lightweight change however, as it requires a rebuild of the - index tables in order to generate the hashes. This can take a long time on databases - that already have a large amount of data. -

        -

        - As a result, in HAPI FHIR JPA 3.6.0, an efficient way of upgrading existing databases - was added. Under this new scheme, columns for the hashes are added but values are not - calculated initially, database indexes are not modified on the HFJ_SPIDX_xxx tables, - and the previous columns are still used for searching as was the case in HAPI FHIR - JPA 3.4.0. -

        -

        - In order to perform a migration using this functionality, the following steps should - be followed: -

        -
          -
        • - Stop your running HAPI FHIR JPA instance (and remember to make a backup of your - database before proceeding with any changes!) -
        • -
        • - Modify your DaoConfig to specify that hash-based searches should not be used, using - the following setting: -
          -
          myDaoConfig.setDisableHashBasedSearches(true);
          -
        • -
        • - Make sure that you have your JPA settings configured to not automatically - create database indexes and columns using the following setting - in your JPA Properties: -
          -
          extraProperties.put("hibernate.hbm2ddl.auto", "none");
          -
        • -
        • - Run the database migrator command, including the entry - -x no-migrate-350-hashes - on the command line. For example: -
          -
          ./hapi-fhir-cli migrate-database -d DERBY_EMBEDDED -u
          -							"jdbc:derby:directory:target/jpaserver_derby_files;create=true" -n "" -p "" -f V3_4_0 -t V3_6_0 -x
          -							no-migrate-350-hashes
          -						
          -
        • -
        • - Rebuild and start your HAPI FHIR JPA server. At this point you should have a working - HAPI FHIR JPA 3.6.0 server that is is still using HAPI FHIR 3.4.0 search indexes. Search hashes - will be generated for any newly created or updated data but existing data will have null - hashes. -
        • -
        • - With the system running, request a complete reindex of the data in the database using - an HTTP request such as the following: -
          -
          GET /$mark-all-resources-for-reindexing
          - Note that this is a custom operation built into the HAPI FHIR JPA server. It should - be secured in a real deployment, so Authentication is likely required for this - call. -
        • -
        • - You can track the reindexing process by watching your server logs, - but also by using the following SQL executed directly against your database: -
          -
          SELECT * FROM HFJ_RES_REINDEX_JOB
          - When this query no longer returns any rows, the reindexing process is complete. -
        • -
        • - At this time, HAPI FHIR should be stopped once again in order to convert it - to using the hash based indexes. -
        • -
        • - Modify your DaoConfig to specify that hash-based searches are used, using - the following setting (this is the default setting, so it could also simply - be omitted): -
          -
          myDaoConfig.setDisableHashBasedSearches(false);
          -
        • -
        • - Execute the migrator tool again, this time omitting the flag option, e.g. -
          -
          ./hapi-fhir-cli migrate-database -d DERBY_EMBEDDED -u
          -							"jdbc:derby:directory:target/jpaserver_derby_files;create=true" -n "" -p "" -f V3_4_0 -t V3_6_0
          -						
          -
        • -
        • - Rebuild, and start HAPI FHIR JPA again. -
        • -
        -
        - -
        - -
        -

        - An interceptor called - CascadingDeleteInterceptor - may be registered against the Server. When this interceptor is enabled, - cascading deletes may be performed using either of the following: -

        -
          -
        • The request may include the following parameter: - _cascade=delete -
        • -
        • The request may include the following header: - X-Cascade: delete -
        • -
        -
        - - - -
        diff --git a/src/site/xdoc/doc_logging.xml b/src/site/xdoc/doc_logging.xml deleted file mode 100644 index 9d8cac2dee3..00000000000 --- a/src/site/xdoc/doc_logging.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - Logging - James Agnew - - - - -
        - -

        - Java has an abundance of logging frameworks, none of which are perfect. Many libraries - depend on one or more of these frameworks but also have dependencies who depend on a - different one. These dependencies can cause conflicts and be very irritating to solve. -

        - - - -

        - If you don't want to spend much time worrying about logging, it's probably - easiest to just include the Logback - JAR along with your application. -

        -

        - Logback is a powerful and flexible framework. To configure it, simply - include a "logback.xml" file on your classpath. The following contents - may be placed in this file to simply log at a suitable level - to the console: -

        - - - - - INFO - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n - - - - - - - -]]> - -

        - For more detail on how logging can be configured, see the - following section. -

        - - - - - - Logging arch diagram - -

        - HAPI uses - SLF4j - for all internal logging. SLF4j is a "logging facade" framework, meaning - that it doesn't actually handle log output (i.e. it isn't actually writing log lines - to disk) but rather it is able to delegate that task to any of a number of - underlying frameworks (e.g. log4j, logback, JDK logging, etc.) -

        - -

        - This means that in order to successfully log anything, you will need to - add two (or three) dependency JARs to your application: -

        -
          -
        • slf4j-api-vXX.jar: This is the SLF4j API and is neccesary for HAPI to function
        • -
        • - An actual logging implementation, as well as its SLF4j binding. For example: -
            -
          • - The recommended logging framework to use is Logback. Logback is absolutely - not neccesary for HAPI to function correctly, but it has a number of nice features - and is a good default choice. To use logback, you would include - logback-vXX.jar. -
          • -
          • - If you wanted to use log4j you would include log4j-vXX.jar - as well as slf4j-log4j-vXX.jar. Log4j is a mature - framework that is very widely used. -
          • -
          • - If you wanted to use JDK logging (aka java.util.Logging) you would include - slf4j-jdk14-vXX.jar. JDK logging is included with - Java but is not particularly full featured compared to many other frameworks. -
          • -
          -
        • -
        - -
        - - - - Logging arch diagram - -

        - Note that HAPI's client uses Apache HttpComponents Client internally, and that - library uses Apache Commons Logging as a logging facade. The recommended approach to - using HAPI is to not include any commons-logging JAR in your application, but rather to - include a copy of jcl-over-slf4j-vXX.jar. This JAR will simulate commons-logging, - but will redirect its logging statements to the same target as SLF4j has been - configured to. -

        - -

        - The diagram at the right shows the chain of command for logging under this scheme. -

        - -

        - Note that some popular libraries (e.g. Spring Framework) also use commons-logging - for logging. As such they may include a commons-logging JAR automatically as - a transitive dependency in Maven. If you are using jcl-over-slf4j and it isn't - working correctly, it is often worth checking the list of JARs included in your - application to see whether commons-logging has also been added. It can then be specifically - excluded in Maven. -

        - -
        - -
        - -
        - -
        - -

        - To enable detailed logging of client requests and responses (what URL is being requested, what headers and payload - are being received, etc.), an interceptor may be added to the client which logs each transaction. See - Logging Requests and Responses for more information. -

        - -
        - -
        - -

        - To enable detailed logging of server requests and responses, - an interceptor may be added to the server which logs each transaction. See - Logging Server Requests for more information. -

        - -
        - - - - diff --git a/src/site/xdoc/doc_narrative.xml b/src/site/xdoc/doc_narrative.xml deleted file mode 100644 index 782ba00f1b7..00000000000 --- a/src/site/xdoc/doc_narrative.xml +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - Narrative Generation - James Agnew - - - - -
        - -

        - HAPI provides a several ways to add - Narrative Text - to your encoded messages. -

        - -

        - The simplest way is to simply place the narrative text directly in the resource - via the - getText() - method. -

        - - - - - - -
        - -
        - -

        - HAPI also comes with a built-in mechanism for automatically generating - narratives based on your resources. -

        - -

        - Warning: This built-in capability is a work in progress, and does not cover - every type of resource or even every attribute in any resource. You should test it - and configure it for your particular use cases. -

        - -

        - HAPI's built-in narrative generation uses the - Thymeleaf library - for templating narrative texts. Thymeleaf provides a simple - XHTML-based syntax which is easy to use and - meshes well with the HAPI-FHIR model objects. -

        - - - -

        - Activating HAPI's built-in narrative generator is as simple - as calling - setNarrativeGenerator. -

        - - - - - - -

        - ...which produces the following output: -

        - - - - -
        -
        John Edward SMITH
        - - - - - -
        Identifier7000135
        Address742 Evergreen Terrace
        Springfield ZZ
        -
        -
        - -]]> - -
        - - - -

        - HAPI currently only comes with built-in support for - a few resource types. Our intention is that people enhance these - templates and create new ones, and share these back with us so that - we can continue to build out the library. To see the current - template library, see the source repository - here. -

        - -

        - Note that these templates expect a few specific CSS definitions - to be present in your site's CSS file. See the - narrative CSS - to see these. -

        - -
        - -
        - -
        - -

        - To use your own templates for narrative generation, - simply create one or more templates, using the Thymeleaf - HTML based syntax. -

        - - - - - -

        - Then create a properties file which describes your - templates. In this properties file, each resource to - be defined has a pair or properties. -

        - -

        - The first (name.class) defines the class name of the resource to define a - template for. The second (name.narrative) defines the path/classpath to the - template file. The format of this path is file:/path/foo.html or classpath:/com/classpath/foo.html -

        - - - -

        - You may also override/define behaviour for datatypes. These datatype narrative - definitions will be used as content within th:narrative blocks - in resource templates. See the example resource template above for an example. -

        - - - -

        - Finally, use the - CustomThymeleafNarrativeGenerator - and provide it - to the FhirContext. -

        - - - - - - -
        - - - -
        diff --git a/src/site/xdoc/doc_resource_references.xml b/src/site/xdoc/doc_resource_references.xml deleted file mode 100644 index 6e4b3f5e220..00000000000 --- a/src/site/xdoc/doc_resource_references.xml +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - Resource References - James Agnew - - - - - -
        - -

        - Resource references are a key part of the HAPI FHIR model, - since almost any resource will have references to other resources - within it. -

        - -

        - The ResourceReferenceDt - type is the datatype for references. This datatype has a number of properties which help - make working with FHIR simple. -

        - -

        - The getReference() method returns an IdDt instance which contains the identity of the - resource being referenced. This is the item which is most commonly populated when - interacting with FHIR. For example, consider the following Patient resource, which - contains a reference to an Organization resource: -

        - - - - - - - - -]]> - -

        - Given a Patient resource obtained by invoking a client operation, a call to - IdDt ref = patient.getManagingOrganization().getReference(); - returns an instance of IdDt which contains the "Organization/112" reference. -

        - -

        - ResourceReferenceDt also has a field for storing actual resource instances however, - and this can be very useful. -

        - - -
        - -
        - -

        - In client code, if a resource reference refers to a resource which was received as a - part of the same response, getResource() will be populated with the - actual resource. This can happen because either the resource was received as a - contained resource, or the resource was received as a separate resource in a bundle. -

        - -
        - -
        - -

        - In server code, you will often want to return a resource which contains - a link to another resource. Generally these "linked" resources are - not actually included in the response, but rather a link to the - resource is included and the client may request that resource directly - (by ID) if it is needed. -

        - -

        - The following example shows a Patient resource being created which will have a - link to its managing organization when encoded from a server: -

        - - - - -

        - Your server code may also wish to add additional resource to a bundle - being returned (e.g. because of an _include directive in the client's request). -

        - -

        - To do this, you can implement your server method to simply return - List<IResource> and then simply add your extra resources to - the list. Another technique however, is to populate the reference as shown - in the example below, but ensure that the referenced resource has an ID set. -

        - -

        - In the following example, the Organization resource has an ID set, so it will not - be contained but will rather appear as a distinct entry in any returned - bundles. Both resources are added to a bundle, which will then have - two entries: -

        - - - - - -

        - This will give the following output: -

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - -
        - - - -

        - On the other hand, if the linked resource - does not have an ID set, the linked resource will - be included in the returned bundle as a "contained" resource. In this - case, HAPI itself will define a local reference ID (e.g. "#1"). -

        - - -

        - This will give the following output: -

        - - - - - - - - - - - - - -]]> - -
        - -

        - Note that you may also "contain" resources manually in your own code if you - prefer. The following example show how to do this: -

        - - - - - - - -
        - -
        - -

        - By default, HAPI will strip resource versions from references between resources. - For example, if you set a reference to Patient.managingOrganization - to the value Patient/123/_history/2, HAPI will encode this - reference as Patient/123. -

        -

        - This is because in most circumstances, references between resources should be - versionless (e.g. the reference just points to the latest version, whatever - version that might be). -

        -

        - There are valid circumstances however for wanting versioned references. If you - need HAPI to emit versionned references, you have a few options: -

        -

        - You can force the parser to never strip versions: -

        - - - - -

        - You can also disable this behaviour entirely on the context (so that it - will apply to all parsers): -

        - - - - -

        - You can also configure HAPI to not strip versions only on certain fields. This - is desirable if you want versionless references in most places but need them - in some places: -

        - - - - -
        - - -
        diff --git a/src/site/xdoc/doc_rest_client.xml b/src/site/xdoc/doc_rest_client.xml deleted file mode 100644 index a55ee31fba8..00000000000 --- a/src/site/xdoc/doc_rest_client.xml +++ /dev/null @@ -1,569 +0,0 @@ - - - - - - - RESTful Client - James Agnew - - - - - -
        - - - -

        - HAPI provides a built-in mechanism for connecting to FHIR RESTful - servers. - The HAPI RESTful client is designed to be easy to set up and - to allow strong - compile-time type checking wherever possible. -

        - -

        - There are two types of RESTful clients provided by HAPI: - The Fluent/Generic client (described below) and - the Annotation - client. - The generic client is simpler to use - and generally provides the faster way to get started. The annotation-driven - client relies on static binding to specific operations to - give better compile-time checking against servers with a specific set of capabilities - exposed. This second model takes more effort to use, but can be useful - if the person defining the specific methods to invoke is not the same person - who is using those methods. -

        - -
        - -
        - -

        - Creating a generic client simply requires you to create an instance of - FhirContext and use that to instantiate a client. -

        -

        - The following example shows how to create a client, and a few operations which - can be performed. -

        - - - - - - -

        - Performance Tip: Note that FhirContext is an expensive object to create, - so you should try to keep an instance around for the lifetime of your application. It - is thread-safe so it can be passed as needed. Client instances, on the other hand, - are very inexpensive to create so you can create a new one for each request if needed - (although there is no requirement to do so, clients are reusable and thread-safe as well). -

        - - -

        - The generic client supports queries using a fluent interface - which is inspired by the fantastic - .NET FHIR API. - The fluent interface allows you to construct powerful queries by chaining - method calls together, leading to highly readable code. It also allows - you to take advantage of intellisense/code completion in your favourite - IDE. -

        -

        - Note that most fluent operations end with an execute() - statement which actually performs the invocation. You may also invoke - several configuration operations just prior to the execute() statement, - such as encodedJson() or encodedXml(). -

        -
        - - - -

        - Searching for resources is probably the most common initial scenario for - client applications, so we'll start the demonstration there. The FHIR search - operation generally uses a URL with a set of predefined search parameters, - and returns a Bundle containing zero-or-more resources which matched the - given search criteria. -

        -

        - Search is a very powerful mechanism, with advanced features such as paging, - including linked resources, etc. See the FHIR - search specification - for more information. -

        - -

        - Note on Bundle types: As of DSTU2, FHIR defines Bundle as a resource - instead of an Atom feed as it was in DSTU1. In code that was written for - DSTU1 you would typically use the ca.uhn.fhir.model.api.Bundle - class to represent a bundle, and that is that default return type for search - methods. If you are implemeting a DSTU2+ server, is recommended to use a - Bundle resource class instead (e.g. ca.uhn.fhir.model.dstu2.resource.Bundle - or org.hl7.fhir.dstu2.model.Bundle). Many of the examples below include - a chained invocation similar to - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class), which - instructs the search method which bundle type should be returned. -

        - -

        - The following example shows how to query using the generic client: -

        - - - - - -

        Search - Multi-valued Parameters (ANY/OR)

        -

        - To search for a set of possible values where ANY should be matched, - you can provide multiple values to a parameter, as shown in the example below. - This leads to a URL resembling ?family=Smith,Smyth -

        - - - - - -

        Search - Multi-valued Parameters (ALL/AND)

        -

        - To search for a set of possible values where ALL should be matched, - you can provide multiple instances of a parameter, as shown in the example below. - This leads to a URL resembling ?address=Toronto&address=Ontario&address=Canada -

        - - - - - -

        Search - Paging

        -

        - If the server supports paging results, the client has a page method - which can be used to load subsequent pages. -

        - - - - - -

        Search - Composite Parameters

        -

        - If a composite parameter is being searched on, the parameter - takes a "left" and "right" operand, each of which is - a parameter from the resource being seached. The following example shows the - syntax. -

        - - - - - -

        Search - By plain URL

        -

        - You can also perform a search using a String URL, instead - of using the fluent method calls to build the URL. This - can be useful if you have a URL you retrieved from - somewhere else that you want to use as a search. -

        - - - - - -

        Search - Other Query Options

        -

        - The fluent search also has methods for sorting, limiting, specifying - JSON encoding, _include, _revinclude, _lastUpdated, _tag, etc. -

        - - - - - -

        Search - Using HTTP POST

        -

        - The FHIR specification allows the use of an HTTP POST to transmit a search to a server instead of - using - an HTTP GET. With this style of search, the search parameters are included in the request body - instead - of the request URL, which can be useful if you need to transmit a search with a large number - of parameters. -

        -

        - The usingStyle() method controls which style to use. By default, GET style is used - unless the client detects that the request would result in a very long URL (over 8000 chars) in which - case the client automatically switches to POST. -

        -

        - An alternate form of the search URL (using a URL ending with_search) was also - supported in FHIR DSTU1. This form is no longer valid in FHIR DSTU2, but HAPI retains support - for using this form in order to interoperate with servers which use it. -

        - - - - - -

        Search - Compartments

        -

        - To search a - resource compartment, - simply use the withIdAndCompartment - method in your search. -

        - - - - - -

        Search - Subsetting (_summary and _elements)

        -

        - Sometimes you may want to only ask the server to include some parts of returned - resources (instead of the whole resource). Typically this is for performance or - optimization reasons, but there may also be privacy reasons for doing this. -

        -

        - To request that the server return only "summary" elements (those elements - defined in the specification with the "Σ" flag), you can use the - summaryMode(SummaryEnum) qualifier: -

        - - - - -

        - To request that the server return only elements from a custom list - provided by the client, you can use the elementsSubset(String...) - qualifier: -

        - - - - - -
        - - -

        - The following example shows how to perform a create - operation using the generic client: -

        - - - - - -

        Conditional Creates

        -

        - FHIR also specifies a type of update called "conditional create", where - a set of search parameters are provided and a new resource is only - created if no existing resource matches those parameters. See the - FHIR specification for more information on conditional creation. -

        - - - - -
        - - -

        - Given a resource name and ID, it is simple to retrieve - the latest version of that resource (a 'read') -

        - - - - -

        - By adding a version string, it is also possible to retrieve a - specific version (a 'vread') -

        - - - - -

        - It is also possible to retrieve a resource given its absolute - URL (this will override the base URL set on the client) -

        - - - - - -

        - See also the page on - ETag Support - for information on specifying a matching version in the - client request. -

        - -
        - - -

        - The following example shows how to perform a delete - operation using the generic client: -

        - - - - -

        Conditional Deletes

        -

        - Conditional deletions are also possible, which is a form where - instead of deleting a resource using its logical ID, you specify - a set of search criteria and a single resource is deleted if - it matches that criteria. Note that this is not a mechanism - for bulk deletion; see the FHIR specification for information - on conditional deletes and how they are used. -

        - - - - -
        - - -

        - Updating a resource is similar to creating one, except that - an ID must be supplied since you are updating a previously - existing resource instance. -

        -

        - The following example shows how to perform an update - operation using the generic client: -

        - - - - - -

        Conditional Updates

        -

        - FHIR also specifies a type of update called "conditional updates", where - insetad of using the logical ID of a resource to update, a set of - search parameters is provided. If a single resource matches that set of - parameters, that resource is updated. See the FHIR specification for - information on how conditional updates work. -

        - - - - - -

        ETags and Resource Contention

        -

        - See also the page on - ETag Support - for information on specifying a matching version in the - client request. -

        - -
        - - -

        - To retrieve the version history of all resources, or all resources of a given type, or - of a specific instance of a resource, you call the history() - method. -

        - - - - - -

        - If you are using a DSTU2 compliant server, you should instead use the - Bundle resource which is found in the DSTU2 structures JAR, as shown - in the syntax below. Note that in both cases, the class name is Bundle, - but the DSTU2 bundle is found in the .resources. package. -

        - - - - - -

        - You can also optionally request that only resource versions - later than a given date, and/or only up to a given count (number) - of resource versions be returned. -

        - - - - -
        - - -

        - The following example shows how to execute a transaction using the generic client: -

        - - - - -
        - - -

        - To retrieve the server's conformance statement, simply call the conformance() - method as shown below. -

        - - - - -
        -
        - -
        -

        - In the FHIR DSTU2 version, operations (referred to as "extended operations") - were added. These operations are an RPC style of invocation, with a set of - named input parameters passed to the server and a set of named output - parameters returned back. -

        -

        - To invoke an operation using the client, you simply need to create the - input - Parameters - resource, then pass that to the operation() fluent method. -

        -

        - The example below shows a simple operation call. -

        - - - - - -

        - Note that if the operation does not require any input parameters, - you may also invoke the operation using the following form. Note that - the withNoParameters still requires you to provide the - type of the Parameters resource so that it can return the correct type in - the response. -

        - - - - - - -

        - By default, the client will invoke operations using the HTTP POST form. - The FHIR specification also allows requests to use the HTTP GET verb - if the operation is idempotent and has no composite/resource parameters. - Use the following form to invoke operation with HTTP GET. -

        - - - - -
        - - -

        - The $validate operation asks the server to test a given resource - to see if it would be acceptable as a create/update on that server. - The client has built-in support for this operation. -

        -

        - If the client is in DSTU1 mode, the method below will invoke the - DSTU1 validation style instead. -

        - - - - - - -
        - - -

        - The $process-message operation asks the server to accept a fhir - message bundle for processing. -

        - - - - - - -
        - -
        - -
        - -

        - This section contains ways of customizing the request sent by the client -

        - - - -

        - The Cache-Control header can be used by the client in a request - to signal to the server (or any cache in front of it) that the client wants specific - behaviour from the cache, or wants the cache to not act on the request altogether. - Naturally, not all servers will honour this header. -

        - -

        - To add a cache control directive in a request: -

        - - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_client_alternate_provider.xml b/src/site/xdoc/doc_rest_client_alternate_provider.xml deleted file mode 100644 index 78fe5e1b712..00000000000 --- a/src/site/xdoc/doc_rest_client_alternate_provider.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - JAX-RS & Alternate HTTP Client Providers - James Agnew - - - - -
        - -

        - By default, the HAPI FHIR client uses the - Apache HTTP Client (HC) - as it's underlying HTTP provider. HC is a very powerful and efficient provider, - so it is generally a good choice. -

        -

        - It can be replaced however by providing an alternate implementation of - IRestfulClientFactory - to the FhirContext. -

        - - - -

        - If you are using HAPI FHIR's client in an environment where other - JAX-RS clients are being used, you may want to use the JAX-RS provider - instead of the Apache HC provider. -

        -

        - Using this provider is as simple as creating an instance and providing it - to the context: -

        - - - - - -

        - Note that this provider is defined in the JAX-RS Server module, so you need - to add the following dependency to your project in order for this to work: -

        -
        
        -	ca.uhn.hapi.fhir
        -	hapi-fhir-jaxrsserver-base
        -	[version]
        -]]>
        - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_client_annotation.xml b/src/site/xdoc/doc_rest_client_annotation.xml deleted file mode 100644 index db1582d7bf4..00000000000 --- a/src/site/xdoc/doc_rest_client_annotation.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - Annotation Client - James Agnew - - - - -
        - -

        - HAPI also provides a second style of client, called the annotation-driven client. - If you are using the - Fluent/Generic Client - do not need to read this page. -

        - -

        - The design of the annotation-driven client - is intended to be similar to that of - JAX-WS, so users of that - specification should be comfortable with - this one. It uses a user-defined interface containing special - annotated methods which HAPI binds to calls against a server. -

        - -

        - The annotation-driven client is particularly useful if you have a server that - exposes a set of specific operations (search parameter combinations, named queries, etc.) - and you want to let developers have a stongly/statically typed interface to that - server. -

        -

        - There is no difference in terms of capability between the two styles of - client. There is simply a difference in programming style and complexity. It - is probably safe to say that the generic client is easier to use and leads to - more readable code, at the expense of not giving any visibility into the - specific capabilities of the server you are interacting with. -

        - - - -

        - The first step in creating an annotation-driven client is to define a - restful client interface. -

        - -

        - A restful client interface class must extend the - IRestfulClient - interface, - and will contain one or more methods which have been - annotated with special annotations indicating which RESTful - operation - that method supports. Below is a simple example of a - resource provider - which supports the - read - operation (i.e. retrieve a single resource by ID) as well as the - search - operation (i.e. find any resources matching a given criteria) for a - specific - search criteria. -

        - -

        - You may notice that this interface looks a lot like the Resource - Provider - which is defined for use by the RESTful server. In fact, it - supports all - of the same annotations and is essentially identical, - other than the - fact that for a client you must use an interface but for a server you - must use a concrete class with method implementations. -

        - - - - - - -

        - You will probably want to add more methods - to your client interface. - See - RESTful Operations - for - lots more examples of how to add methods for various operations. -

        - -
        - - - -

        - Once your client interface is created, all that is left is to - create a FhirContext and instantiate the client and you are - ready to - start using it. -

        - - - - - - -
        - - - -

        - Restful client interfaces that you create will also extend - the interface - IRestfulClient, - which comes with some helpful methods for configuring the way that - the client will interact with the server. -

        -

        - The following snippet shows how to configure the cliet to explicitly - request JSON or XML responses, and how to request "pretty printed" responses - on servers that support this (HAPI based servers currently). -

        - - - - - - -
        - - - -

        - The following is a complete example showing a RESTful client - using - HAPI FHIR. -

        - - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_client_examples.xml b/src/site/xdoc/doc_rest_client_examples.xml deleted file mode 100644 index e3bc069e57d..00000000000 --- a/src/site/xdoc/doc_rest_client_examples.xml +++ /dev/null @@ -1,138 +0,0 @@ - - - - - RESTful Client Examples - James Agnew - - - - - - -
        - -

        - This page contains examples of how to use the client to perform - complete tasks. If you have an example you could contribute, we'd - love to hear from you! -

        - - - -

        - The following example shows how to post a transaction with two resources, - where one resource contains a reference to the other. A temporary ID (a UUID) - is used as an ID to refer to, and this ID will be replaced by the server by - a permanent ID. -

        - - - - - - -

        - This code creates the following transaction bundle:
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> -

        - -

        - The server responds with the following response. Note that - the ID of the already existing patient is returned, and the - ID of the newly created Observation is too.
        - - - - - - - - - - - - - - - - - - - - - - - - -]]> -

        - -
        - - - -

        - This example - shows how to load all pages of a bundle by fetching each page one-after-the-other and then - joining the resuts. -

        - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_client_http_config.xml.vm b/src/site/xdoc/doc_rest_client_http_config.xml.vm deleted file mode 100644 index b34df1be3ac..00000000000 --- a/src/site/xdoc/doc_rest_client_http_config.xml.vm +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - Client Configuration - James Agnew - - - -
        - -

        - This page outlines ways that the client can be configured - for specific behaviour. -

        - -
        - - - -
        - - -

        - By default, the client will query the server before the very first - operation to download the server's conformance/metadata statement - and verify that the server is appropriate for the given client. - This check is only done once per server endpoint for a given - FhirContext. -

        -

        - This check is useful to prevent bugs or unexpected behaviour - when talking to servers. It may introduce unneccesary overhead - however in circumstances where the client and server are known - to be compatible. The following example shows how to disable this - check. -

        - - - - -
        - - - -

        - By default, HAPI will scan each model type it encounters - as soon as it encounters it. This scan includes a check for - all fields within the type, and makes use of reflection to do this. -

        -

        - While this process is not particularly significant on reasonably - performant machines (one benchmark showed that this takes - roughly 0.6 seconds to scan all types on one developer workstation), on some devices - (e.g. Android phones where every millisecond counts) - it may be desirable to defer this scan. -

        -

        - When the scan is deferred, objects will only be scanned when they - are actually accessed, meaning that only types that are - actually used in an application get scanned. -

        -

        - The following example shows how to defer model scanning: -

        - - - - - -
        - -
        - -
        - -

        - RESTful clients (both Generic and Annotation-Driven) use - Apache HTTP Client - as a provider by default (except on Android, where - OkHttp - is the default). -

        -

        - The Apache HTTP Client is very powerful and extremely - flexible, - but can be confusing at first to configure, because of the low-level - approach that - the library uses. -

        - -

        - In many cases, the default configuration should suffice. HAPI FHIR - also encapsulates some of the more common configuration settings you - might want to use (socket timesouts, proxy settings, etc.) so that these - can be configured through HAPI's API without needing to understand the - underlying HTTP Client library. -

        - -

        - This configuration is provided by accessing the - IRestfulClientFactory - class from the FhirContext. -

        - -

        - Note that individual requests and responses - can be tweaked using - Client Interceptors. - This approach is generally useful for configuration involving - tweaking the HTTP request/response, such as adding authorization headers - or logging. -

        - - -

        - The following example shows how to configure low level - socket timeouts for client operations. -

        - - - - -
        - - - -

        - The following example shows how to configure the use of an HTTP - proxy in the client. -

        - - - - - - -
        - - - -

        - As of HAPI FHIR 2.0, an alternate client implementation - is available. This client replaces the low-level - Apache HttpClient implementation with the - Square - OkHttp - library. -

        -

        - Changing HTTP implementations should be mostly - transparent (it has no effect on the actual FHIR - semantics which are transmitted over the wire) but - might be useful if you have an application that - uses OkHttp in other parts of the application and - has specific configuration for that library. -

        -

        - Note that as of HAPI FHIR 2.1, OkHttp is the default - provider on Android, and will be used without any - configuration being required. This is done because - HttpClient is deprecated on Android and has caused - problems in the past. -

        -

        - To use OkHttp, first add the library as a dependency to your project POM: -

        - - ca.uhn.hapi.fhir - hapi-fhir-client-okhttp - ${hapi_stable_version} -]]> - -

        - Then, set the client factory to use OkHttp. -

        - - - - -
        -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_client_interceptor.xml b/src/site/xdoc/doc_rest_client_interceptor.xml deleted file mode 100644 index d762a959cb3..00000000000 --- a/src/site/xdoc/doc_rest_client_interceptor.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - RESTful Client - James Agnew - - - - -
        - -

        - Both generic clients and annotation-driven clients support - Client Interceptors, - which may be registered in order to provide specific behaviour to each - client request. -

        - -

        - The following section shows some sample interceptors which may be used. -

        - - - -

        - The following example shows how to configure your client to - use a specific username and password in every request. -

        - - - - - - -
        - - - -

        - The following example shows how to configure your client to - inject a bearer token authorization header into every request. This - is used to satisfy servers which are protected using OAUTH2. -

        - - - - - - -
        - - - - -

        - The LoggingInterceptor can be used to - log every transaction. The interceptor is flexible and can be configured to be extremely - verbose (logging entire transactions including HTTP headers and payload bodies) - or simply to log request URLs, or some combination in between. -

        - - - - - - -
        - - - -

        - The CookieInterceptor can be used to - add an HTTP Cookie header to each request created by the client. -

        - - - - - - -
        - - - -

        - The GZipContentInterceptor compresses outgoing contents. - With this interceptor, if the client is transmitting resources to the server - (e.g. for a create, update, transaction, etc.) the content will be GZipped - before transmission to the server. -

        - - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_etag.xml b/src/site/xdoc/doc_rest_etag.xml deleted file mode 100644 index 4d8def62cc6..00000000000 --- a/src/site/xdoc/doc_rest_etag.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - ETags - James Agnew - - - - - -
        - -

        - HAPI provides support for - HTTP ETags, which are - a standard way of providing faster reads when content has not changed and - optimistic locking for updates. -

        - -
        - - - -
        - -

        - ETag support is automatically enabled in the RESTful server. - This has the following effects: -

        -
          -
        • - Read/VRead - method responses will include an - ETag header, noting the version - being returned. -
        • -
        • - If an incoming Read method includes an If-None-Match header with - the same version as the latest version being returned, the server will automatically - return an HTTP 304 Not Modified instead of returning the - resource body. -
        • -
        - - - -

        - To disable ETag support, simply invoke the - setETagSupport method, as in the following example. -

        - - - - - -
        - - - -
        - - - - diff --git a/src/site/xdoc/doc_rest_operations.xml b/src/site/xdoc/doc_rest_operations.xml deleted file mode 100644 index 56275a79c47..00000000000 --- a/src/site/xdoc/doc_rest_operations.xml +++ /dev/null @@ -1,1870 +0,0 @@ - - - - - - - RESTful Operations - James Agnew - - - -
        - - -

        - This page shows the operations which can be implemented on - HAPI - RESTful Servers, as well as - Annotation Clients. - Most of the examples shown here show how to implement a server - method, but to perform an equivalent call on an annotation - client you simply put a method with the same signature in your - client interface. -

        - - -
        - - - - - -
        - -

        - The - - read - - operation retrieves a resource by ID. It is annotated with the - @Read - annotation, and has a single parameter annotated with the - @IdParam - annotation. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient/111 -

        - -

        - The following snippet shows how to define a client interface - to handle a read method. -

        - - - - - - - -
        - - - - - -
        - -

        - The - - vread - - operation retrieves a specific version of a resource with a given ID. - To support vread, simply add "version=true" to your @Read annotation. This - means that the read method will support both "Read" and "VRead". The IdDt - may or may not have the version populated depending on the client request. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient/111/_history/2 -

        - - -
        - - - - - -
        - -

        - The - - update - - operation updates a specific resource instance (using its ID), and optionally - accepts a version ID as well (which can be used to detect version conflicts). -

        -

        - Update methods must be annotated with the - @Update - annotation, and have a parameter annotated with the - @ResourceParam - annotation. This parameter contains the resource instance to be created. - See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]). -

        -

        - In addition, the method may optionally have a parameter annotated with the - @IdParam - annotation, or they may obtain the ID of the resource being updated from - the resource itself. Either way, this ID comes from the URL passed in. -

        -

        - Update methods must return an object of type - MethodOutcome - . This - object contains the identity of the created resource. -

        -

        - The following snippet shows how to define an update method on a server: -

        - - - - - - -

        - Example URL to invoke this method (this would be invoked using an HTTP PUT, - with the resource in the PUT body): -
        - http://fhir.example.com/Patient -

        - -

        - The following snippet shows how the corresponding client interface - would look: -

        - - - - - - -

        Conditional Updates

        -

        - If you wish to suport conditional updates, you can add a parameter - tagged with a - @ConditionalOperationParam - annotation. If the request URL contains search parameters instead of a - resource ID, then this parameter will be populated. -

        - - - - - - -

        - Example URL to invoke this method (this would be invoked using an HTTP PUT, - with the resource in the PUT body): -
        - http://fhir.example.com/Patient?identifier=system%7C00001 -

        - - -

        Accessing The Raw Resource Payload

        -

        - If you wish to have access to the raw resource payload as well as the parsed value - for any reason, you may also add parameters which have been annotated - with the @ResourceParam of type - String (to access the raw resource body) and/or - EncodingEnum (to determine which encoding was used) -

        -

        - The following example shows how to use these additonal data elements. -

        - - - - - - -
        -

        Prefer Header / Returning the resource body

        -

        - If you want to allow clients to request that the server return - the resource body as a result of the transaction, you may wish to - return the updated resource in the returned MethodOutcome. -

        -

        - In this type of request, the client adds a header containing - Prefer: return=representation which indicates to the server - that the client would like the resource returned in the response. -

        -

        - In order for the server to be able to honour this request, the - server method should add the updated resource to the MethodOutcome object - being returned, as shown in the example below. -

        - - - - - - -

        Contention Aware Updating

        - -

        - As of FHIR DSTU2, FHIR uses the ETag header to - provide "conention aware updating". Under this scheme, a client - may create a request that contains an ETag specifying the version, - and the server will fail if the given version is not the latest - version. -

        -

        - Such a request is shown below. In the following example, the update will - only be applied if resource "Patient/123" is currently at version "3". - Otherwise, -

        -
        - -

        - If a client performs a contention aware update, the ETag version will be - placed in the version part of the IdDt/IdType that is passed into the - method. For example: -

        - - - - - - -
        -
        - - - - - -
        - -

        - The - - delete - - operation retrieves a specific version of a resource with a given ID. It takes a single - ID parameter annotated with an - @IdParam - annotation, which supplies the ID of the resource to delete. -

        - - - - - - -

        - Delete methods are allowed to return the following types: -

        -
          -
        • - void - : This method may return - void - , in which case - the server will return an empty response and the client will ignore - any successful response from the server (failure responses will still throw - an exception) -
        • -
        • - - MethodOutcome - - : - This method may return - MethodOutcome - , - which is a wrapper for the FHIR OperationOutcome resource, which may optionally be returned - by the server according to the FHIR specification. -
        • -
        - -

        - Example URL to invoke this method (HTTP DELETE): -
        - http://fhir.example.com/Patient/111 -

        - -

        Conditional Deletes

        - -

        - The FHIR specification also allows "conditional deletes". A conditional - delete uses a search style URL instead of a read style URL, and - deletes a single resource if it matches the given search parameters. - The following example shows how to -

        - - - - - - -

        - Example URL to perform a conditional delete (HTTP DELETE): -
        - http://fhir.example.com/Patient?identifier=system%7C0001 -

        - - -
        - - - - - -
        - -

        - The - - create - - operation saves a new resource to the server, allowing the server to - give that resource an ID and version ID. -

        -

        - Create methods must be annotated with the - @Create - annotation, and have a single parameter annotated with the - @ResourceParam - annotation. This parameter contains the resource instance to be created. - See the @ResourceParam for information on the types allowed for this parameter (resource types, String, byte[]). -

        -

        - Create methods must return an object of type - MethodOutcome - . This - object contains the identity of the created resource. -

        -

        - The following snippet shows how to define a server create method: -

        - - - - - - -

        - Example URL to invoke this method (this would be invoked using an HTTP POST, - with the resource in the POST body): -
        - http://fhir.example.com/Patient -

        - -

        - The following snippet shows how the corresponding client interface - would look: -

        - - - - - - -

        Conditional Creates

        - -

        - The FHIR specification also allows "conditional creates". A conditional - create has an additional header called If-None-Exist - which the client will supply on the HTTP request. The client will - populate this header with a search URL such as Patient?identifier=foo. - See the FHIR specification for details on the semantics for correctly - implementing conditional create. -

        -

        - When a conditional create is detected (i.e. when the create request contains - a populated If-None-Exist header), if a method parameter annotated - with the - @ConditionalOperationParam - is detected, it will be populated with the value of this header. -

        - - - - - - -

        - Example URL and HTTP header to perform a conditional create: -
        - http://fhir.example.com/Patient
        If-None-Exist: Patient?identifier=system%7C0001
        -

        - -

        Prefer Header / Returning the resource body

        -

        - If you wish to allow your server to honour the Prefer - header, the same mechanism shown above for - Prefer Header for Updates should be used. -

        - -

        Accessing The Raw Resource Payload

        -

        - The create operation also supports access to the raw payload, - using the same semantics as raw payload access - for the update operation. -

        - - -
        - - - - - -
        - -

        - The - - search - - operation returns a bundle - with zero-to-many resources of a given type, matching a given set of parameters. -

        - - - -

        - The following example shows a search with no parameters. This operation - should return all resources of a given type (this obviously doesn't make - sense in all contexts, but - does for some resource types). -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient -

        - -
        - - -

        - To allow a search using given search parameters, add one or more parameters - to your search method and tag these parameters as either - @RequiredParam - or - @OptionalParam - . -

        - -

        - This annotation takes a "name" parameter which specifies the parameter's - name (as it will appear in the search URL). FHIR defines standardized parameter - names for each - resource, and these are available as constants on the - individual HAPI resource - classes. -

        - -

        - Parameters which take a string as their format should use the - StringParam - type. They may also use normal java String, although it is - not possible to use the :exact qualifier in that case. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient?family=SMITH -

        - -
        - - -

        - The "token" type is used for parameters which have two parts, such as - an idnetifier (which has a system URI, as well as the actual identifier) - or a code (which has a code system, as well as the actual code). - For example, the search below can be used to search by - identifier (e.g. search for an MRN). -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient?identifier=urn:foo|7000135 -

        - -
        - - - -

        - The FHIR specification provides a sytax for specifying - dates+times (but for simplicity we will just say dates here) - as search criteria. -

        - -

        - Dates may be optionally prefixed with a qualifier. For example, the - string - >=2011-01-02 - means any date on or after 2011-01-02. -

        - -

        - To accept a qualified date parameter, use the - DateParam parameter type. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Observation?birthdate=>=2011-01-02 -

        - -

        - Invoking a client of thie type involves the following syntax: -

        - - - - - - -
        - - - - -

        - A common scenario in searches is to allow searching for resources - with values (i.e timestamps) within a range of dates. -

        - -

        - FHIR allows for multiple parameters with the same key, and interprets - these as being an "AND" set. So, for example, a range of - date=>=2011-01-01&date=<2011-02-01 -
        - can be interpreted as any date within January 2011. -

        - -

        - The following snippet shows how to accept such a range, and combines it - with a specific identifier, which is a common scenario. (i.e. Give me a list - of observations for a - specific patient within a given date range) -

        - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Observation?subject.identifier=7000135&date=>=2011-01-01&date=<2011-02-01 -

        - -

        - Invoking a client of this type involves the following syntax: -

        - - - - - - -

        Unbounded Ranges

        - -

        - Note that when using a date range parameter, it is also possible for - the client to request an "unbounded" range. In other words, a range that - only a start date and no end - date, or vice versa. -

        - -

        - An example of this might be the following URL, which refers to any Observation - resources for the given MRN and having a date after 2011-01-01. -
        - http://fhir.example.com/Observation?subject.identifier=7000135&date=>=2011-01-01 -
        - When such a request is made of a server (or to make such a request from a client), - the - getLowerBound() - or - getUpperBound() - property of the - DateRangeParam - object will be set to - null - . -

        - -
        - - - -

        - Quantity parameters allow a number with units and a comparator -

        - -

        - The following snippet shows how to accept such a range, and combines it - with a specific identifier, which is a common scenario. (i.e. Give me a list - of observations for a - specific patient within a given date range) -

        - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Observation?value-quantity=<=123.2||mg -

        - -
        - - - -

        - Many search parameters refer to resource references. For instance, the Patient - parameter "provider" refers to the resource marked as the managing organization - for patients. -

        -

        - Reference parameters use the - ReferenceParam - type. Reference parameters are, in their most basic form, just a pointer to another - resource. For example, you might want to query for DiagnosticReport resources where the - subject (the Patient resource that the report is about) is Patient/123. The following - example shows a simple resource reference parameter in use. -

        - - - - - - -

        Chained Resource References

        - -

        - References may also support a "chained" value. This is a search parameter name - on the target resource. For example, you might want to search for DiagnosticReport - resources by subject, but use the subject's last name instead of their resource ID. - In this example, you are chaining "family" (the last name) to "subject" (the patient). - The net result in the query string would look like:
        - http://fhir.example.com/DiagnosticReport?subject.family=SMITH
        - What this query says is "fetch me all of the DiagnosticReport resources - where the subject (Patient) of the report has the family (name) of - 'SMITH'". -

        - -

        - There are two ways of dealing with chained parameters in your methods: static chains and - dynamic chains. Both are equally valid, although dyamic chains might lead to somewhat - more compact and readable code. -

        - - -

        Dynamic Chains

        - -

        - Chained values must be explicitly declared through the use - of a whitelist (or blacklist). The following example shows how to declare a - report with an allowable chained parameter: -

        - - - - - -

        - You may also specify the whitelist value of - "" to allow an empty chain (e.g. ther resource ID) - and this can be combined with other values, as shown below: -

        - - - - - - -

        - If you are handling multiple types of chained parameters in a single method, - you may want to convert the reference parameter type into something more - convenient before using its value. The following example shows how to do that. -

        - - - - - - -

        Static Chains

        - -

        - It is also possible to explicitly state a chained value right in the parameter name. - This is useful if you want to only support a search by a specific given chained - parameter. It has the added bonus that you can use the correct parameter type of - the chained parameter (in this case a TokenParameter because the Patient.identifier - parameter is a token) -

        - - - - - - - - - - -

        - Composite search parameters incorporate two parameters in a single - value. Each of those parameters will themselves have a parameter type. -

        - -

        - In the following example, Observation.name-value-date is shown. This parameter - is a composite of a string and a date. Note that the composite parameter types - (StringParam and DateParam) must be specified in both the annotation's - compositeTypes field, as well as the generic types for the - CompositeParam method parameter itself. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Observation?name-value-date=PROCTIME$2001-02-02 -

        - -
        - - - -

        - Search methods may take multiple parameters, and these parameters - may (or may not) be optional. - To add a second required parameter, annotate the - parameter with - @RequiredParam - . - To add an optional parameter (which will be passed in as - null - if no value - is supplied), annotate the method with - @OptionalParam - . -

        - -

        - You may annotate a method with any combination of as many @RequiredParam and as many @OptionalParam - parameters as you want. It is valid to have only @RequiredParam - parameters, or - only @OptionalParam parameters, or any combination of the two. -

        - -

        - If you wish to create a server that can accept any combination of a large number - of parameters, (this is how the various reference servers behave, as well as the - public HAPI server) - the easiest way to accomplish this is to simply create one method - with all allowable parameters, each annotated as @OptionalParam. -

        - -

        - On the other hand, if you have specific combinations of parameters you wish to - support (a common scenario if you are building FHIR on top of existing data sources - and only have certain indexes you can use) you could create multiple search methods, - each with specific required and optional parameters matching the database indexes. -

        - -

        - The following example shows a method with two parameters. -

        - - - - - - -

        - Example URLs to invoke this method: -
        - http://fhir.example.com/Patient?family=SMITH -
        - http://fhir.example.com/Patient?family=SMITH&given=JOHN -

        - - - - - -

        - It is possible to accept multiple values of a single parameter - as well. This is useful in cases when you want to return a list - of resources with criteria matching a list of - possible values. - See the - FHIR Specification - for more information. -

        - -

        - The FHIR specification allows two types of composite parameters: -

        -
          -
        • - Where a parameter may accept multiple comma separated values within a single value string - (e.g. - ?language=FR,NL - ) this is treated as an - OR - relationship, and - the search should return elements matching either one or the other. -
        • -
        • - Where a parameter may accept multiple value strings for the same parameter name - (e.g. - ?language=FR&language=NL - ) this is treated as an - AND - relationship, - and the search should return only elements matching both. -
        • -
        - -

        - It is worth noting that according to the FHIR specification, you can have an - AND relationship combining multiple OR relationships, but not vice-versa. In - other words, it's possible to support a search like - ("name" = ("joe" or "john")) AND ("age" = (11 or 12)) but not - a search like - ("language" = ("en" AND "fr") OR ("address" = ("Canada" AND "Quebec")) -

        - -

        OR Relationship Query Parameters

        - -

        - To accept a composite parameter, use a parameter type which implements the - IQueryParameterOr - interface. -

        -

        - Each parameter type (StringParam, TokenParam, etc.) has a corresponding parameter - which accepts an OR list of parameters. These types are called "[type]OrListParam", for example: - StringOrListParam and TokenOrListParam. -

        -

        - The following example shows a search for Observation by name, where a list of - names may be passed in (and the expectation is that the server will return Observations - that match any of these names): -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Observation?name=urn:fakenames|123,urn:fakenames|456 -

        - -

        AND Relationship Query Parameters

        - -

        - To accept a composite parameter, use a parameter type which implements the - IQueryParameterAnd - interface (which in turn encapsulates the corresponding IQueryParameterOr types). -

        -

        - An example follows which shows a search for Patients by address, where multiple string - lists may be supplied by the client. For example, the client might request that the - address match ("Montreal" OR "Sherbrooke") AND ("Quebec" OR "QC") using - the following query: -
        - http://fhir.example.com/Patient?address=Montreal,Sherbrooke&address=Quebec,QC -

        -

        - The following code shows how to receive this parameter using a - StringAndListParameter, - which can handle an AND list of multiple OR lists of strings. -

        - - - - - - -

        - Note that AND parameters join multiple OR parameters together, but - the inverse is not true. In other words, it is possible in FHIR - to use AND search parameters to specify a search criteria of - (A=1 OR A=2) AND (B=1 OR B=2) - but it is not possible to specify - (A=1 AND B=1) OR (A=2 AND B=2) (aside from - in very specific cases where a composite parameter has been - specifically defined). -

        - -

        AND Relationship Query Parameters for Dates

        - -

        - Dates are a bit of a special case, since it is a common scenario to want to match - a date range (which is really just an AND query on two qualified date parameters). - See the section below on date ranges - for an example of a DateRangeParameter. -

        - -
        - - - -

        - FHIR allows clients to request that specific linked resources be included - as contained resources, which means that they will be "embedded" in a special - container called - "contained" within the parent resource. -

        - -

        - HAPI allows you to add a parameter for accepting includes if you wish - to support them for specific search methods. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/DiagnosticReport?identifier=7000135&_include=DiagnosticReport.subject -

        - -

        - It is also possible to use a String type for the include parameter, - which is more convenient if only a single include (or null for none) - is all that is required. -

        - - - - - - -
        - - - -

        - To add support for reverse includes (via the _revinclude parameter), - use the same format as with the _include parameter (shown above) - but add reverse=true to the @IncludeParam - annotation, as shown below. -

        - - - - - - -
        - - - -

        - FHIR supports - named queries - , - which may have specific behaviour defined. The following example shows how to create a Search - operation with a name. -

        - -

        - This operation can only be invoked by explicitly specifying the given query name - in the request URL. Note that the query does not need to take any parameters. -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient?_query=namedQuery1&someparam=value -

        - -
        - - - -

        - FHIR supports - sorting - according to a specific set of rules. -

        - -

        - According to the specification, sorting is requested by the client using a - search param as the sort key. For example, when searching Patient resources, - a sort key of "given" requests the "given" search param as the sort key. That - param maps to the values in the field "Patient.name.given". -

        - -

        - Sort specifications can be passed into handler methods by adding a parameter - of type - SortSpec, - which has been annotated with the - @Sort - annotation, as shown in the following example: -

        - - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient?_identifier=urn:foo|123&_sort=given -

        - -
        - - - -

        - It is also possible to annotate search methods and/or parameters with - the - @Description - annotation. This annotation allows you to add a description of the method - and the individual parameters. These descriptions will be placed in the - server's conformance statement, which can be helpful to anyone who is developing - software against your server. -

        - - - - - - -
        - - -
        - - - - - -
        - -

        - The - - validate - - operation tests whether a resource passes business validation, and would be - acceptable for saving to a server (e.g. by a create or update method). -

        -

        - Note on FHIR versions: - In FHIR DSTU1 the validate operation used a URL resembling http://example.com/Patient/_validate - with a resource in the HTTP POST body. In FHIR DSTU2, validate has been changed to use the - extended operation mechanism. It now uses a URL - resembling http://example.com/Patient/$validate and takes a - Parameters resource as input in the method body.

        - The mechanism described below may be used for both DSTU1 and DSTU2+ servers, and HAPI - will automatically use the correct form depending on what FHIR version the - server is configured to use. -

        -

        - Validate methods must be annotated with the - @Validate - annotation, and have a parameter annotated with the - @ResourceParam - annotation. This parameter contains the resource instance to be created. -

        -

        - Validate methods may optionally also have a parameter - oftype IdDt annotated with the - @IdParam - annotation. This parameter contains the resource ID (see the - FHIR specification - for details on how this is used) -

        -

        - Validate methods must return normally if the resource validates successfully, - or throw an - UnprocessableEntityException - or - InvalidRequestException - if the validation fails. -

        -

        - Validate methods must return either: -

        -
          -
        • - void - - The method should throw an exception for a validation failure, or return normally. -
        • -
        • - An object of type - MethodOutcome - . The - MethodOutcome may optionally be populated with an OperationOutcome resource, which - will be returned to the client if it exists. -
        • -
        -

        - The following snippet shows how to define a server validate method: -

        - - - - - - -

        - In the example above, only the @ResourceParam parameter is technically required, but - in DSTU2 you are encouraged to also add the following parameters: -

        -
          -
        • @Validate.Mode ValidationModeEnum mode - This is the validation mode (see the FHIR specification for information on this)
        • -
        • @Validate.Profile String profile - This is the profile to validate against (see the FHIR specification for more information on this)
        • -
        - -

        - Example URL to invoke this method (this would be invoked using an HTTP POST, - with a Parameters resource in the POST body): -
        - http://fhir.example.com/Patient/$validate -

        - - -
        - - - - - -
        - -

        - FHIR defines that a FHIR Server must be able to export a conformance statement, - which is an instance of the - Conformance - resource describing the server itself. -

        - -

        - The HAPI FHIR RESTful server will automatically export such - a conformance statement. See the - RESTful Server - documentation for more information. -

        - -

        - If you wish to override this default behaviour by creating - your own metadata provider, you simply need to define a class - with a method annotated using the - @Metadata - annotation. -

        - - - - - -

        - To create a Client which can retrieve a Server's conformance - statement is simple. First, define your Client Interface, using - the @Metadata annotation: -

        - - - - - -

        - Then use the standard - RESTful Client - mechanism for instantiating - a client: -

        - - - - - - -
        - - - - - -
        - -

        - The - transaction - action is among the most challenging parts of the FHIR specification to implement. It allows the - user to submit a bundle containing a number of resources to be created/updated/deleted as a single - atomic transaction. -

        - -

        - HAPI provides a skeleton for implementing this action, although most of the effort - will depend on the underlying implementation. The following example shows - how to define a transaction method. -

        - - - - - -

        - Transaction methods require one parameter annotated with @TransactionParam, and that - parameter may be of type List<IResource> or Bundle. -

        - -

        - In terms of actually implementing the method, unfortunately there is only so much help - HAPI will give you. One might expect HAPI to automatically delegate the individual - operations in the transaction to other methods on the server but at this point it - does not do that. There is a lot that transaction needs to handle - (making everything atomic, replacing placeholder IDs across multiple resources - which may even be circular, handling operations in the right order) and - so far we have not found a way for the framework to do this in a generic way. -

        -

        - What it comes down to is the fact that transaction is a tricky thing to implement. - For what it's worth, you could look at our JPA module's "transaction" method in - our source repository - to see how we implemented transaction in the JPA server. -

        - -

        - Example URL to invoke this method: -
        - POST http://fhir.example.com/
        - (note that the content of this POST will be a bundle) -

        - - - -
        - - - - - -
        - -

        - Not yet implemented - Get in touch if you would like to help! -

        - -
        -
        - - - - - -
        - -

        - The - - history - - operation retrieves a historical collection of all versions of a single resource - (instance history) - , all resources of a given type - (type history) - , - or all resources of any type on a server - (server history) - . -

        -

        - History methods must be annotated with the - @History - annotation, and will have additional requirements depending on the kind - of history method intended: -

        -
          -
        • - For an - Instance History - method, the method must have a parameter - annotated with the - @IdParam - annotation, indicating the ID of the resource for which to return history. -
            -
          • - For a server - implementation, the method must either be defined in a - resource provider - or have a - type() - value in the @History annotation if it is - defined in a - plain provider - . -
          • -
          -
        • -
        • - For a - Type History - method, the method must not have any @IdParam parameter. -
            -
          • - For a server - implementation, the method must either be defined in a - resource provider - or have a - type() - value in the @History annotation if it is - defined in a - plain provider - . -
          • -
          -
        • -
        • - For a - Server History - method, the method must not have any @IdParam parameter, and - must not have a - type() - value specified in - the @History annotation. -
            -
          • - In a server implementation, the method must - be defined in a - plain provider - . -
          • -
          -
        • -
        -

        - The following snippet shows how to define a history method on a server. Note that - the following parameters are both optional, but may be useful in - implementing the history operation: -

        - -
      • - The @Since method argument implements the _since - parameter and should be of type DateTimeDt or DateTimeType -
      • -
      • - The @At method argument implements the _at - parameter and may be of type - DateRangeParam, - DateTimeDt or DateTimeType -
      • -
        - - - - - - -

        - The following snippet shows how to define various history methods in a client. -

        - - - - - - - -
        - - -
        - -

        - HAPI FHIR includes basic support for the - - patch - - operation. This support allows you to perform patches, but does not - include logic to actually implement resource patching in the server - framework (note that the JPA server does include a patch implementation). -

        -

        - The following snippet shows how to define a patch method on a server: -

        - - - - - - -
        - - - - - - -
        - -

        - When implementing a server operation, there are a number of failure conditions - specified. For example, an - Instance Read - request might specify an unknown - resource ID, or a - Type Create - request might contain an - invalid resource which can not be created. -

        -

        - In these cases, an appropriate exception should be thrown. The HAPI RESTful - API includes a set of exceptions extending - BaseServerResponseException - which represent specific HTTP failure codes. -

        -

        - See the - Exceptions List - for a complete list of these exceptions. Note that these exceptions are all - unchecked - exceptions, so they do not need to ne explicitly declared in the method - signature. -

        - - -
        - - - - - -
        - -

        - FHIR RESTful servers may support a feature known as tagging. Tags are a set of - named flags called "terms" (with an optional accompanying human friendly name - called a "label", - and an optional namespace called a "scheme"). -

        -

        - Tags have very specific semantics, which may not be - obvious simply by using the HAPI API. It is important to review the specification - pages - here - and - here - before attempting to implement tagging in your own applications. -

        - - - -

        - Tags are stored within a resource object, in the - IResource.html#getResourceMetadata() - map, under the key - TAG_LIST. -

        - -

        - In a server implementation, you may populate your tags into the - returned resource(s) and HAPI will automatically place these tags into - the response headers (for read/vread) or the bundle category tags (for search). - The following example illustrates how to return tags from a server method. This - example shows how to supply tags in a read method, but the same approach applies - to vread and search operations as well. -

        - - - - - - -

        - In a client operation, you simply call the read/vread/search method as you - normally would (as described above), and if any tags have been returned - by the server, these may be accessed from the resource metadata. -

        - - - - - - -
        - - - -

        - Within a Type Create - or Instance Update method, it is - possible for the client to specify a set of tags to be stored - along with the saved resource instance. -

        -

        - Note that FHIR specifies that in an update method, any tags supplied - by the client are copied to the newly saved version, as well as any - tags the existing version had. -

        - -

        - To work with tags in a create/update method, the pattern used in the - read examples above is simply revered. In a server, the resource which - is passed in will be populated with any tags that the client supplied: -

        - - - - - - -
        - - - -

        - FHIR also provides a number of operations to interact directly - with tags. These methods may be used to retrieve lists of tags - that are available on the server, or to add or remove tags from - resources without interacting directly with those resources. -

        - -

        - On a server these methods may be placed in a plain provider, or in a resource - provider in the case of resource type specific methods. -

        - - - - - -

        - On a client, the methods are defined in the exact same way, except that - there is no method body in the client interface. -

        - -
        - -
        - - - - - -
        - - The _summary and _elements parameters are - automatically handled by the server, so no coding is required to make this - work. If you wish to add parameters to manually handle these fields however, - the following example shows how to access these. - - - - - - - - -
        - - - - - -
        - -

        - FHIR defines a mechanism for logically grouping - resources together called compartments. -

        -

        - To define a search by compartment, you simply need to add the compartmentName attribute - to the @Search annotation, and add an @IdParam parameter. -

        -

        - The following example shows a search method in a resource provider which returns - a compartment. Note that you may also add @RequiredParam and - @OptionalParam parameters to your compartment search method. -

        - - - - - -

        - Example URL to invoke this method: -
        - http://fhir.example.com/Patient/123/Condition -

        - - -
        - -
        - -

        - FHIR extended operations are a special type of RPC-style invocation you - can perform against a FHIR server, type, or resource instance. These invocations - take a - Parameters - resource as input, and return either another Parameters resource or a different resource type. -

        - -

        - To define an operation, a method should be placed in a - Resource Provider - class if the operation works against a resource type (e.g. Patient) - or a resource instance (e.g. Patient/123), or on a - Plain Provider - if the operation works against the server (i.e. it is global and not resource specific). -

        - - - -

        - To implement a type-specific operation, - the method should be annotated with the - @Operation tag, and should have an - @OperationParam tag for each named parameter that - the input Parameters resource may be populated with. The following - example shows how to implement the Patient/$everything - method, defined in the FHIR specification. -

        - - - - - -

        - Example URL to invoke this operation (HTTP request body is Parameters resource): -
        - POST http://fhir.example.com/Patient/$everything -

        - -
        - - - -

        - To create an instance-specific operation (an operation which takes the - ID of a specific resource instance as a part of its request URL), - you can add a parameter annotated with the @IdParam annotation, - of type IdDt. The following example show how to implement the - Patient/[id]/$everything operation. -

        - - - - - -

        - Example URL to invoke this operation (HTTP request body is Parameters resource): -
        - http://fhir.example.com/Patient/123/$everything -

        - -
        - - - -

        - FHIR allows operation parameters to be of a - Search parameter type - (e.g. token) instead of a FHIR datatype (e.g. Coding). -

        -

        - To use a search parameter type, any of the search parameter - types listed in - Search - may be used. For example, the following is a simple operation method declaration - using search parameters: -

        - - - - - -

        - Example URL to invoke this operation (HTTP request body is Parameters resource): -
        - http://fhir.example.com/$find-matches?date=2011-01-02&code=http://system|value -

        - -

        - It is also fine to use collection types for search parameter types - if you want to be able to accept multiple values. For example, - a List<TokenParam> could be used if you want - to allow multiple repetitions of a given token parameter (this is - analogous to the "AND" semantics in a search). - A TokenOrListParam could be used if you want to allow - multiple values within a single repetition, separated by comma (this - is analogous to "OR" semantics in a search). -

        -

        For example:

        - - - - - -
        - - - -

        - Server operations do not operate on a specific resource type or - instance, but rather operate globally on the server itself. The following - example show how to implement the - $closure operation. Note that the concept parameter - in the example has a cardinality of 0..* according to the - FHIR specification, so a List<Coding> - is used as the parameter type. -

        - - - - - -

        - Example URL to invoke this operation (HTTP request body is Parameters resource): -
        - http://fhir.example.com/$closure -

        - -
        - - - -

        - In all of the Extended Operation examples above, the return - type specified for the operation is a single Resource instance. This is - a common pattern in FHIR defined operations. However, it is also - possible for an extended operation to be defined with multiple - and/or repeating OUT parameters. In this case, you can return - a Parameters resource directly. -

        - -
        - - - -

        - The FHIR specification notes that if an operation is - idempotent - (which means roughly that it does not modity any data or state - on the server) then it may be invoked with an HTTP GET - instead of an HTTP POST. -

        -

        - If you are implementing an operation which is idempotent, - you should mark your operation with - idempotent=true, - as shown in some of the examples above. The default value - for this flag is false, meaning that operations - will not support HTTP GET by default. -

        -

        - Note that the HTTP GET form is only supported if the operation - has only primitive parameters (no complex parameters or resource parameters). - If a client makes a request containing a complex parameter, the - server will respond with an HTTP 405 Method Not Supported. -

        -
        - - - -

        - For some operations you may wish to bypass the HAPI FHIR - standard request parsing and/or response generation. In this - case you may use the manualRequest() and - manualResponse() attributes on the @Operation - annotation. -

        -

        - The following example shows an operation that parses the - request and generates a response (by echoing back the request). -

        - - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_server.xml b/src/site/xdoc/doc_rest_server.xml deleted file mode 100644 index dcbf88717ba..00000000000 --- a/src/site/xdoc/doc_rest_server.xml +++ /dev/null @@ -1,745 +0,0 @@ - - - - - - - RESTful Server - James Agnew - - - - - -
        - -

        - HAPI provides a built-in mechanism for adding FHIR's RESTful Server - capabilities to your applications. The HAPI RESTful Server is Servlet - based, so it should be easy to deploy to any of the many compliant - containers that exist. -

        - -

        - Setup is mostly done using simple annotations, which means that it should - be possible to create a FHIR compliant server quickly and easily. -

        - - - - -

        - The first step in creating a FHIR RESTful Server is to define one or - more resource providers. A resource provider is a class which is - able to supply exactly one type of resource to be served up. -

        - -

        - For example, if you wish to allow your server to serve up Patient, - Observation and Location resources, you will need three resource - providers. -

        - -

        - A Resource provider class must implement the - IResourceProvider interface, - and will contain one or more methods which have been - annotated with special annotations indicating which RESTful operation - that method supports. Below is a simple example of a resource provider - which supports the - read - operation (i.e. retrieve a single resource by ID) as well as the - search - operation (i.e. find any resources matching a given criteria) for a specific - search criteria. -

        - - - - - - - - - -

        - You will probably wish to add more methods - to your resource provider. See - RESTful Operations for - lots more examples of how to add methods for various operations. -

        -

        - For now, we will move on to the next step though, which is creating - the actual server to hold your resource providers and deploying that. - Once you have this working, you might want to come back and - start adding other operations. -

        - -
        - - - -

        - Once your resource providers are created, your next step is to - define a server class. -

        - -

        - HAPI provides a class called - RestfulServer, which - is a specialized Java Servlet. To create a server, you simply create a class - which extends RestfulServer as shown in the example below. -

        - - - - - - - -
        - - - - -

        - Defining one provider per resource is a good strategy to keep - code readable and maintainable, but it is also possible to put - methods for multiple resource types in a provider class. Providers - which do not implement the - IResourceProvider - (and therefore are not bound to one specific resource type) are known as - Plain Providers. -

        -

        - A plain provider may implement any - RESTful operation, but will generally - need to explicitly state what type of resource it applies to. If the method directly - returns a resource or a collection of resources (as in an - instance read or - type search operation) - the resource type will be inferred automatically. If the method returns a - Bundle - resource, it is necessary to explicitly specify the resource type - in the method annotation. The following example shows this: -

        - - - - - -

        - In addition, some methods are not resource specific. For example, the - system history operation - returns historical versions of all resource types on a server, - so it needs to be defined in a plain provider. -

        - -

        - Once you have defined your plain providers, they are passed to the - server in a similar way to the resource providers. -

        - - - - - - - - -

        - The server will return data in a number of places that includes the - complete "identity" of a resource. Identity in this case refers to the - web address that a user can use to access the resource. -

        -

        - For instance, if your server is hosted at - http://foo.com/fhir - and your resource provider returns a Patient resource with the ID "123", - the server should translate that ID to "http://foo.com/fhir/Patient/123". -

        -

        - The server will attempt to determine what the base URL should be based on - what the request it receives looks like, but if it is not getting - the right address you may wish to use a different "address strategy". -

        -

        - The simplest way to do this is to configure the server to use a hardcoded - base URL, which means that the server won't try to figure out the - "http://foo.com/fhir" part of the URL but will instead just use a fixed - value you supply. This is shown in the following example: -

        - - - - - -

        Other Strategies

        -

        - See the - IServerAddressStrategy - JavaDoc (specifically the list of "All Known Implementing Classes") to see - other strategies that are available. -

        - -
        - - - -

        - Once you have created your resource providers and your restful server class, - you can bundle these into a WAR file and you are ready to deploy to - any JEE container (Tomcat, Websphere, Glassfish, etc). -

        - -

        - Bundling a servlet into a WAR file and deploying it to an application server - is beyond the scope of this page, but there are many good tutorials on how - to do this. -

        - -
        - -
        - -
        - -

        - The HAPI FHIR RESTful Server will automatically export a - capability statement (or a - conformance statement for DSTU2 and prior), - as required by the - FHIR Specification. -

        -

        - This statement is automatically generated based on the various annotated methods which are - provided to the server. This behaviour may be modified by creating a new class - containing a method annotated with a - @Metadata Operation - and then passing an instance of that class to the - setServerConformanceProvider method - on your server. -

        - - - -

        - If you have a need to add your own content (special extensions, etc.) to your - server's conformance statement, but still want to take advantage of HAPI's automatic - conformance generation, you may wish to extend - ServerConformanceProvider. -

        - -

        - In your own class extending this class, you can override the getServerConformance() method - to provide your own implementation. In this method, call - super.getServerConformance() to obtain the built-in conformance statement and then - add your own information to it. -

        - -

        - Note that if you are adding items during each invocation you should be aware that by default the - same instance is cached by ServerConformanceProvider. This can result in an ever-growing - conformance statement. You must call setCache(false); in - the constructor of your new conformance provider to avoid this behaviour. -

        - -
        - -
        - -
        - -

        - The Search and History operations both return a bundle - which contain zero or more resources. FHIR RESTful servers may optionaly - support paging responses, meaning that (for example) if a search returns 500 - resources, the server can return a bundle containing only the first 20 and a link - which will return the next 20, etc. -

        - -

        - By default, RESTful servers will not page, but will rather return all resources - immediately in a single bundle. There are two complimentary parts to the paging support: paging - prividers, and bundle providers. -

        - - - -

        - To support paging, a server must have an IPagingProvider - implementation set. The paging provider is used to store resource - return lists between incoming calls by clients. -

        - -

        - A paging provider provides two key methods: -

        -
          -
        • - storeResultList, which takes a bundle provider (see below) - and stores it for later retrieval. This might be by simply keeping it - in memory, but it might also store it on disk, in a database, etc. This - method must return a textual ID which can be used to retrieve this - list later. -
        • -
        • - retrieveResultList, which takes an ID obtained by a - previous call to storeResultList and returns the corresponding - result list. -
        • -
        - -

        - Note that the IPagingProvider is intended to be simple and implementable and - you are encouraged to provide your own implementations. -

        - -

        - The following example shows a server implementation with paging - support. -

        - - - - - - -
        - - - -

        - If a server supports a paging provider, a further optimization is to - also use a bundle provider. A bundle provider simply takes the place of - the List<IResource> return type in your provider methods. -

        - -

        - When using a bundle provider however, the server will only request small sublists - of resources as they are actually being returned. This allows servers to optimize - by not loading all resources into memory until they are actually needed. -

        - -

        - One implementation of a bundle provider is shown below. This provider example works - by only keeping the resource IDs in memory, but there are other possible implementation - strategies that would work as well. -

        - -

        - Note that the IBundleProvider is intended to be simple and implementable and - you are encouraged to provide your own implementations. -

        - - - - - - -
        - - - -

        - By default, the paging system uses parameters that are embedded into the - page links for the start index and the page size. This is useful for servers that - can retrieve arbitrary offsets within a search result. For example, - if a given search can easily retrieve "items 5-10 from the given search", then - the mechanism above works well. -

        -

        - Another option is to use "named pages", meaning that each - page is simply assigned an ID by the server, and the next/previous - page is requested using this ID. -

        -

        - In order to support named pages, the IPagingProvider must - implement the - retrieveResultList(String theSearchId, String thePageId) - method. -

        -

        - Then, individual search/history methods may return a - BundleProviderWithNamedPages instead of a simple - IBundleProvider. -

        -
        - -
        - - -
        - -

        - Different RESTful methods will have different requirements - in terms of the method parameters they require, as described - in the RESTful Operations - page. -

        - -

        - In addition, there are several parameters you may add - in order to meet specific needs of your application. -

        - - - -

        - In some cases, it may be useful to have access to the - underlying HttpServletRequest and/or HttpServletResponse - objects. These may be added by simply adding one or both - of these objects as method parameters. -

        - - - - - - -
        - - - -

        - FHIR allows for the a number of special behaviours where only certain - portions of resources are returned, instead of the entire resource body. - These behaviours are automatically supported in HAPI (as of HAPI 1.2) - and no additional effort needs to be taken. -

        - -

        - The following behaviours are automatically supported by the HAPI server: -

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        ParameterDescription
        _summary=true - Resources will be returned with any elements not marked as summary elements - omitted. -
        _summary=text - Only the narrative portion of returned resources will be returned. For a read/vread - operation, the narrative will be served with a content type of text/html. - for other operations, a Bundle will be returned but resources will only include - the text element. -
        _summary=data - The narrative (text) portion of the resource will be omitted. -
        _summary=count - For a search, only Bundle.count will be returned. -
        _elements=[element names] - Only the given top level elements of returned resources will be returned, e.g for - a Patient search: _elements=name,contact -
        -
        - -
        - - - -
        - -

        - Within your RESTful operations, you will generally be returning - resources or bundles of resources under normal operation. During - execution you may also need to propagate errors back to the client - for a variety of reasons. -

        - - -

        - By default, HAPI generates appropriate error responses for a several - built-in conditions. For example, if the user makes a request for - a resource type that does not exist, or tries to perform a search - using an invalid parameter, HAPI will automatically generate - an HTTP 400 Invalid Request, and provide an - OperationOutcome resource as response containing details about - the error. -

        - -

        - Similarly, if your method implementation throws any exceptions - (checked or unchecked) instead - of returning normally, the server will usually* automatically - generate an HTTP 500 Internal Error and generate - an OperationOutcome with details about the exception. -

        - -

        - * Note that certain exception types will generate other response - codes, as explained below. -

        -
        - - -

        - In many cases, you will want to respond to client requests - with a specific HTTP error code (and possibly your own error message - too). Sometimes this is a requirement of the FHIR specification - (e.g. the "validate" operation requires a response of - HTTP 422 Unprocessable Entity if the validation fails). - Sometimes this is simply a requirement of your specific application - (e.g. you want to provide application specific HTTP status codes for - certain types of errors) -

        - -

        - To customize the error that is returned by HAPI's server methods, you - must throw an exception which extends HAPI's - BaseServerResponseException - class. Various exceptions which extend this class will generate - a different HTTP status code. -

        -

        - For example, the - ResourceNotFoundException - causes HAPI to return an HTTP 404 Resource Not Found. A complete list - of available exceptions is available - here. -

        -

        - If you wish to return an HTTP status code for which there is no - pre-defined exception, you may throw the - UnclassifiedServerFailureException, - which allows you to return any status code you wish. -

        -
        - -

        - By default, HAPI will automatically generate an OperationOutcome - which contains details about the exception that was thrown. You may - wish to provide your own OperationOutcome instead. In this - case, you may pass one into the constructor of the - exception you are throwing. -

        - - - - -
        -
        - -
        - -

        - Your RESTful server should now support the methods you have declared. Here are a - few helpful tricks for interacting with the server: -

        - -

        - Pretty Printing: The HAPI RESTful server supports a called - _pretty, which can be used to request that responses be pretty-printed (indented for - easy reading by humans) by setting the value to true. This can be useful in testing. An example URL for this might be:
        - http://example.com/fhir/Patient/_search?name=TESTING&_pretty=true -

        - -
        - -
        - -

        - Server operations will often return a resource or a bundle of resources. These - types will contain one or more resource instances, but also specify a set of - metadata describing that resource. -

        - -

        - For example, resources have a "published" and "updated" date, referring to - the date/time the resource was originally created and the date/time the - resource was last updated respectively. For operations which return a single - resource, these values are returned via HTTP headers. For operations which - return a bundle, these values are returned via elements within the - bundle's "entry" tag. -

        - -

        - Bundles may also contain a set of links, such as an "alternate" link to - a resource, or a "search" link. -

        - -

        - Populating these metadata elements is done via the - IResource#getResourceMetadata() - method. The following example shows how to set various metadata elements on - a resource being returned. -

        - - - - - -
        - -
        - -

        - Resource providers may optionally want to be notified when the server they are registered - with is being destroyed, so that they can perform cleanup. In this case, a method - annotated with the - @Destroy annotation can be added (this method should be public, return void, - and take no parameters). -

        -

        - This method will be invoked once by the RestfulServer when it is shutting down. -

        - -
        - -
        - -

        - A complete example showing how to implement a RESTful server can - be found in our Git repo here: - https://github.com/jamesagnew/hapi-fhir/tree/master/restful-server-example -

        - -

        - Hopefully this will be available as a separate download soon, but currently it may - be used to demonstrate a fully working server project. -

        - -
        - - - -
        - -

        - If you wish to allow a single endpoint to support multiple - tenants, you may supply the server with a multitenancy provider. -

        -

        - This means that additional logic will be performed during request - parsing to determine a tenant ID, which will be supplied to - resource providers. This can be useful in servers that have - multiple distinct logical pools of resources hosted on the - same infrastructure. -

        - - -

        - Using URL Base Multitenancy means that an additional element - is added to the path of each resource between the server base - URL and the resource name. For example, if your restful server - is deployed to http://acme.org:8080/baseDstu3 - and user wished to access Patient 123 for Tenant "FOO", the - resource ID (and URL to fetch that resource) would be - http://acme.org:8080/FOO/Patient/123. -

        - -

        - To enable this mode on your server, simply provide the - UrlBaseTenantIdentificationStrategy to the - server as shown below: -

        - - - - - - -

        - Your resource providers can then use a RequestDetails parameter to - determine the tenant ID: -

        - - - - - -
        - -
        - -
        - -

        - The HAPI FHIR server may be configured using the - RestfulServer#setElementsSupport to enable extended - support for the _elements filter. -

        -

        - In standard mode, elements are supported exactly as described in the - Elements Documentation - in the FHIR specification. -

        -

        - In extended mode, HAPI FHIR provides the same behaviour as described in the - FHIR specification, but also enabled the following additional options: -

        -
          -
        • - Values may be prepended using Resource names in order to apply the - elements filter to multiple resources. For example, the following - parameter could be used to apply elements filtering to both the - DiagnosticReport and Observation resource in a search result:
          - _elements=DiagnosticReport.subject,DiagnosticReport.result,Observation.value -
        • -
        • - Values may be prepended with a wildcard star in order to apply them - to all resource types. For example, the following parameter could - be used to include the subject field in all resource - types:
          - _elements=*.subject -
        • -
        • - Values may include complex paths. For example, the following parameter could - be used to include only the code on a coded element:
          - _elements=Procedure.reasonCode.coding.code -
        • -
        • - Elements may be excluded using the :exclude modifier - on the elements parameter. For example, the following parameter - could be used to remove the resource metadata (meta) element from - all resources in the response:
          - _elements:exclude=*.meta
          - Note that this can be used to suppress the SUBSETTED tag - which is automatically added to resources when an _elements - parameter is applied. -
        • -
        -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_server_interceptor.xml b/src/site/xdoc/doc_rest_server_interceptor.xml deleted file mode 100644 index 85518a12400..00000000000 --- a/src/site/xdoc/doc_rest_server_interceptor.xml +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - - Server Interceptors - James Agnew - - - - - -
        - -

        - The RESTful server provides a powerful mechanism for adding cross-cutting behaviour - (e.g. requests, such as authnorization, auditing, fancy output, logging, etc.) - to each incoming request that it processes. This mechanism consists of defining one or - more interceptors that will be invoked at defined points in the processing of - each incoming request. -

        - - Interceptors - -

        - Interceptors will intercept the incoming request, and can take action such as - logging or auditing it, or examining/injecting headers. They can optionally choose - to handle the request directly and the cancel any subsequent processing (in other words, - the interceptor can choose to supply a response to the client, and can then signal - to the server that it does not need to do so). -

        -

        - Interceptors - may also be notified of responses prior to those responses being served to a client, - and may audit or even cancel the response. The diagram on the right shows the - lifecycle of a normal (non failing) request which is subject to an interceptor. -

        - -

        - Interceptors must implement the - IServerInterceptor - interface (or extend the convenience - InterceptorAdapter - class provided). The RESTful server will normally invoke the interceptor at several - points in the execution of the client request. -

        - -
          -
        • - Before any processing at all is performed on the request, - incomingRequestPreProcessed will be invoked. This can be useful - if you wish to handle some requests completely outside of HAPI's processing - mechanism. -
            -
          • - If this method returns true, processing continues to the - next interceptor, and ultimately to the next phase of processing. -
          • -
          • - If this method returns false, processing stops immediately. - This is useful if the interceptor wishes to supply its own response - by directly calling methods on the HttpServletResponse -
          • -
          • - If this method throws any subclass of - BaseServerResponseException, - processing is stopped immedicately and the corresponding status is returned to the client. - This is useful if an interceptor wishes to abort the request (e.g. because - it did not detect valid credentials) -
          • -
          -
        • -
        • - Once the request is classified (meaning that the URL and request headers are - examined to determine exactly what kind of request is being made), - incomingRequestPostProcessed will be invoked. This method has - an additional parameter, the - RequestDetails - object which contains details about what operation is about to be - called, and what request parameters were receievd with that request. -
            -
          • - If this method returns true, processing continues to the - next interceptor, and ultimately to the next phase of processing. -
          • -
          • - If this method returns false, processing stops immediately. - This is useful if the interceptor wishes to supply its own response - by directly calling methods on the HttpServletResponse -
          • -
          • - If this method throws any subclass of - BaseServerResponseException, - processing is stopped immedicately and the corresponding status is returned to the client. - This is useful if an interceptor wishes to abort the request (e.g. because - it did not detect valid credentials) -
          • -
          -
        • -
        • - Once the request is being handled, - incomingRequestPreHandled will be invoked. This method is useful in that - it provides details about the FHIR operation being invoked (e.g. is this a "read" or a "create"? what - is the resource type and ID of the resource being accessed, etc.). This method can be - useful for adding finer grained access controls. Note that incomingRequestPreHandled - is not able to directly supply a response, but it may throw a - BaseServerResponseException - to abort processing. -
        • -
        • - After the operation is handled (by invoking the corresponding ResourceProvider or PlainProvider method), - but before the actual response is returned to the client, - the outgoingResponse method is invoked. - This method also has details about the request in its parameters, but also - receives a copy of the response that is about to be returned. Note that - there are three implementations of outgoingResponse: The server - will invoke the one which corresponds to the response type - of the operation being invoked (resource, bundle, etc.) -
        • -
        - -
        - - - Interceptors - -

        - In the event of an exception being thrown within the server, the interceptor - method - handleException - will be called. This applies both to HAPI-FHIR defined exceptions thrown within resource provider methods - you have created as well as unexpected exceptions such as NullPointerException thrown - at any point in the handling chain. -

        -

        - In general, you will want to return true from the handleException - method, which means that processing continues normally (RestfulServer will return an - HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown). -

        -

        - However, you may override the server's built-in exception handling by returning - false. In this case, you must provide your own response by - interacting with the HttpServletResponse object which is - passed in. -

        -
        - -
        - - -

        - To register an interceptor to the server, simply call - either registerInterceptor or setInterceptors - on your RestfulServer instance. -

        -

        - Note that order is important: The server will invoke - incomingRequestPreProcessed and incomingRequestPostProcessed - in the same order that they are registered to the server. The server will - invoke outgoingResponse in the reverse order to the - order in which the interceptors were registered. This means that interceptors - can be thought of as "wrapping" the request. -

        - -
        - -
        - -
        - -

        - HAPI also provides built-in interceptors which may be useful. Links to the code for each interceptor - is also provided, to give examples of how interceptors are written. -

        - - - - -

        - The - LoggingInterceptor - (code) - can be used to generate a new log line (via SLF4j) for each incoming request. LoggingInterceptor - provides a flexible message format that can be used to provide a customized level - of detail about each incoming request. -

        - -

        - The following example shows how to register a logging interceptor within - a FHIR RESTful server. -

        - - - - - -

        - This interceptor will then produce output similar to the following: -

        - - - - - - - -

        - The - ExceptionHandlingInterceptor - (code) - can be used to customize what is returned to the client and what is logged when the server throws an - exception for any reason (including routine things like UnprocessableEntityExceptions thrown as a matter of - normal processing in a create method, but also including unexpected NullPointerExceptions thrown by client code). -

        - -

        - The following example shows how to register an exception handling interceptor within - a FHIR RESTful server. -

        - - - - - - - - - -

        - The - ResponseHighlighterInterceptor - (code) - detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead - of just the raw text. In other words, if a user uses a browser to request "http://foo/Patient/1" by typing - this address into their URL bar, they will get nice formatted HTML back with a human readable version - of the content. This is helpful for testers. -

        -

        - To see an example of how this looks, see our demo server using the following example - query: - http://fhirtest.uhn.ca/baseDstu2/Patient -

        -

        - The following example shows how to register this interceptor within - a FHIR RESTful server. -

        - - - - - -
        - - - -

        - The - RequestValidatingInterceptor - and - ResponseValidatingInterceptor - can be used to perform validation of resources on their way into and out of the server respectively. -

        -

        - The RequestValidatingInterceptor looks at resources coming into the server (e.g. for create, - update, $operations, transactions, etc.) and validates them. The ResponseValidatingInterceptor - looks at resources being returned by the server (e.g. for read, search, $operations, etc.) and - validates them. -

        -

        - These interceptors can be configured to add headers to the response, fail the response - (returning an HTTP 422 and throwing an exception in the process), or to add to the - OperationOutcome returned by the server. -

        -

        - See the Validation Page for information on - available - IValidatorModule - validation modules. Any of the Resource Validators - listed on that page can be enabled in these interceptors (note that the Parser Validators - can not). -

        -

        - The following example shows how to register this interceptor within - a FHIR RESTful server. -

        - - - - - -
        - - - -

        - HAPI FHIR includes an interceptor which can be used to - implement CORS support on your server. See HAPI's - CORS Documentation for information - on how to use this interceptor. -

        - -
        - - - -

        - Some security audit tools require that servers return an HTTP 405 if - an unsupported HTTP verb is received (e.g. TRACE). The - BanUnsupprtedHttpMethodsInterceptor - (code) - can be used to accomplish this. -

        - -
        - - - -

        - If you wish to override the value of Resource.meta.source using the value - supplied in an HTTP header, you can use the - CaptureResourceSourceFromHeaderInterceptor - (code) - to accomplish this. -

        - -
        - -
        - -
        - -

        - Creating your own interceptors is easy. HAPI-FHIR provides a class called - InterceptorAdapter which you can extend and then override any - methods you wish. The following example shows a simple request counter. -

        - - - - - -

        - The following example shows an exception handling interceptor which - overrides the built-in exception handling by providing a custom response. -

        - - - - - -
        - -
        - -

        - The HAPI JPA Server is an added layer on top of the HAPI - REST server framework. When you -

        - -

        - When an interceptor is registered against a RestfulServer which is backed by the - HAPI JPA Server, - the incomingRequestPreHandled method will be called once for most - operations (e.g. a FHIR create), but in the case where the client - performs a FHIR transaction that method might be called multiple - times over the course of a single client invocation. For example if the transaction - contained a single create, the incomingRequestPreHandled method will - be called twice: once to indicate the transaction, and once to indicate the create. -

        - -

        - This behaviour can be useful in cases where you want to audit exactly what was done - over the course of a request. Since a transaction can contain creates, updates, and - even more nested transactions, this behaviour ensures that you are notified for each - activity. -

        - -

        - You may also choose to create interceptors which implement the - more specialized - IJpaServerInterceptor - interface, as this interceptor adds additional methods which are called during the JPA - lifecycle. -

        - -

        - Note that this documentation used to erroneously suggest that in order to achieve - this behaviour you needed to register the interceptor with the DaoConfig. In actual fact this - did not make any difference, and registering interceptors with the DaoConfig has now been - deprecated since it does not achieve anything extra. -

        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_server_jaxrs.xml b/src/site/xdoc/doc_rest_server_jaxrs.xml deleted file mode 100644 index 3ad9c720d55..00000000000 --- a/src/site/xdoc/doc_rest_server_jaxrs.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - JAX-RS Server - James Agnew - - - - -
        -

        - The standard server is implemented using Servlet technology. A module - exists which implements the server using JAX-RS technology. - This enables the usage of existing Java EE interceptors and annotations. This module does not provide the full set of features. - - The server currently supports - Conformance Statements, - @Read, - @Search, - @Create, - @Update, - @Delete and - @Operation. -

        -

        - The primary intention for this project is to ensure that other web technologies (JAX-RS in this case) can be used together with the base-server functionality. - An example server can be found in the Git repo here. -

        - - -

        - The set-up of a JAX-RS server goes beyond the scope of this documentation. The implementation of the server follows the same pattern as the standard server. It is required - to put the correct annotation on the methods in the Resource Providers in order to be able to call them. -

        - -

        - Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The @Path - annotation needs to define the resource path. The @Produces annotation - needs to declare the produced formats. The constructor needs to pass the class of the object explicitely in order to avoid problems with proxy classes in a Java EE environment. - It is necessary to extend the abstract class - AbstractJaxRsResourceProvider. - - - - -

        - -

        - - Extended Operations require the correct JAX-RS ( - @Path, - @GET or - @POST) annotations. The body of the method needs to call the - method AbstractJaxRsResourceProvider#customOperation - with the correct parameters. The server will then call the method with corresponding name. - - - - -

        - -

        - In order to create the conformance profile, a conformance provider class needs to be deployed which exports the provider's conformance statements. - These providers need to be returned as the result of - the method AbstractJaxRsResourceProvider#getProviders. - This method is called once, during PostConstruct. - - - - -

        -
        - -
        - -
        - -

        - A complete example showing how to implement a JAX-RS RESTful server can - be found in our Git repo here: - https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jaxrsserver-example -

        - -
        - - - -
        diff --git a/src/site/xdoc/doc_rest_server_security.xml b/src/site/xdoc/doc_rest_server_security.xml deleted file mode 100644 index c6bdd974571..00000000000 --- a/src/site/xdoc/doc_rest_server_security.xml +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - Server Security - - - - - -
        - -

        - Security is a complex topic which goes far beyond the scope of HAPI FHIR. - HAPI does provide mechanisms which can be used to implement security in - your server however. -

        - -

        - Because HAPI FHIR's REST server is based on the Servlet API, you may use any - security mechanism which works in that environment. Some serlvet containers - may provide security layers you can plug into. The rest of this page - does not explore that method, but rather looks at HAPI FHIR hooks that can - be used to implement FHIR specific security. -

        - - - -

        - Background reading: Wikipedia - Authentication -

        -

        - Server security is divided into three topics: -

        -
          -
        • - Authentication (AuthN): Is verifying that the user is who they say they - are. This is typically accomplished by testing a username/password in the request, - or by checking a "bearer token" in the request. -
        • -
        • - Authorization (AuthZ): Is verifying that the user is allowed to perform - the given action. For example, in a FHIR application you might use AuthN to test that - the user making a request to the FHIR server is allowed to access the server, but - that test might determine that the requesting user is not permitted to perform - write operations and therefore block a FHIR create operation. This is - AuthN and AuthZ in action. -
        • -
        • - Consent and Audit: Is verifying that a user has rights to see/modify the specific - resources they are requesting, applying any directives to mask data being returned to the - client (either partially or completely), and creating a record that the - event occurred. -
        • -
        - -
        - -
        - -
        - -

        - The Server Interceptor - framework can provide an easy way to test for credentials. The following - example shows a simple interceptor which tests for HTTP Basic Auth. -

        - - - - - - - - -

        - Note that if you are implementing HTTP Basic Auth, you may want to - return a WWW-Authenticate header with the response. - The following snippet shows how to add such a header with a custom - realm: -

        - - - - - -
        - -
        - -
        - -

        - HAPI FHIR 1.5 introduced a new interceptor, the - AuthorizationInterceptor. -

        -

        - This interceptor can help with the complicated task of determining whether a user - has the appropriate permission to perform a given task on a FHIR server. This is - done by declaring a set of rules that can selectively allow (whitelist) and/or selectively - block (blacklist) requests. -

        - -

        - AuthorizationInterceptor has been well tested, but it is impossible to - predeict every scenario and environment in which HAPI FHIR will be used. - Use with caution, and do lots of testing! We welcome - feedback and suggestions on this feature. Please get in - touch if you'd like to help test, have suggestions, etc. -

        - -

        - The AuthorizationInterceptor works by allowing you to declare - permissions based on an individual request coming in. In other - words, you could have code that examines an incoming request and - determines that it is being made by a Patient with ID 123. You - could then declare that the requesting user has access to read and - write any resource in compartment "Patient/123", which corresponds - to any Observation, MedicationOrder etc with a subject of - "Patient/123". On the other hand, another request - might be detemrined to belong to an administrator user, and - could be declared to be allowed to do anything. -

        - -

        - The AuthorizationInterceptor is used by subclassing it and then registering your - subclass with the RestfulServer. The following example shows a subclassed - interceptor implementing some basic rules: -

        - - - - - - - - -

        - The AuthorizationInterceptor works by examining the client request - in order to determine whether "write" operations are legal, and looks at - the response from the server in order to determine whether "read" operations - are legal. -

        - -
        - - - -

        - When authorizing a read operation, the AuthorizationInterceptor - always allows client code to execute and generate a response. - It then examines the response that would be returned before - actually returning it to the client, and if rules do not permit - that data to be shown to the client the interceptor aborts the - request. -

        - -

        - Note that there are performance implications to this mechanism, - since an unauthorized user can still cause the server to fetch data - even if they won't get to see it. This mechanism should be comprehensive - however, since it will prevent clients from using various features - in FHIR (e.g. _include or _revinclude) to - "trick" the server into showing them date they shouldn't be allowed to - see. -

        - -

        - See the following diagram for an example of how this works. -

        - - Write Authorization - -
        - - - -

        - Write operations (create, update, etc.) are typically authorized - by the interceptor by examining the parsed URL and making a decision - about whether to authorize the operation before allowing Resource Provider - code to proceed. This means that client code will not have a chance to execute - and create resources that the client does not have permissions to create. -

        - -

        - See the following diagram for an example of how this works. -

        - - Write Authorization - -
        - - - -

        - There are a number of situations where the REST framework doesn't - actually know exactly what operation is going to be performed by - the implementing server code. For example, if your server implements - a conditional update operation, the server might not know - which resource is actually being updated until the server code - is executed. -

        -

        - Because client code is actually determining which resources are - being modified, the server can not automatically apply security - rules against these modifications without being provided hints - from client code. -

        -

        - In this type of situation, it is important to manually - notify the interceptor chain about the "sub-operation" being performed. - The following snippet shows how to notify interceptors about - a conditional create. -

        - - - - - - -
        - - -

        - The FHIR patch operation - presents a challenge for authorization, as the incoming request often contains - very little detail about what is being modified. -

        -

        - In order to properly enforce authorization on a server that - allows the patch operation, a rule may be added that allows all - patch requests, as shown below. -

        -

        - This should be combined with server support for - Authorizing Sub-Operations - as shown above. -

        - - - - - -
        - - - -

        - The AuthorizationInterceptor has the ability to direct individual - rules as only applying to a single tenant in a multitenant - server. The following example shows such a rule. -

        - - - - - -
        - -
        - -
        - -

        - HAPI FHIR 4.0.0 introduced a new interceptor, the - ConsentInterceptor. -

        -

        - The consent interceptor may be used to examine client requests to apply - consent directives and create audit trail events. Like the AuthorizationInterceptor - above, this interceptor is not a complete working solution, but instead is a - framework designed to make it easier to implement local policies. -

        -

        - The consent interceptor has several primary purposes: -

        -
          -
        • - It can reject a resource from being disclosed to the user by examining it - while calculating search results. This calculation is performed very early - in the process of building search results, in order to ensure that - in many cases the user is unaware that results have been removed. -
        • -
        • - It can redact results, removing specific elements before they are - returned to a client. -
        • -
        • - It can create audit trail records (e.g. using an AuditEvent resource) -
        • -
        • - It can apply consent directives (e.g. by reading relevant Consent resources) -
        • -
        • - The consent service suppresses search the total being returned in - Bundle.total for search results. -
        • -
        -

        - The ConsentInterceptor requires a user-supplied instance of the - IConsentService - interface. The following shows a simple example of an IConsentService implementation: -

        - - - - - -
        - -
        - -

        - HAPI FHIR 3.7.0 introduced a new interceptor, the - SearchNarrowingInterceptor. -

        -

        - This interceptor is designed to be used in conjunction with AuthorizationInterceptor. It - uses a similar strategy where a dynamic list is built up for each request, but the - purpose of this interceptor is to modify client searches that are received (after - HAPI FHIR received the HTTP request, but before the search is actually performed) - to restrict the search to only search for specific resources or compartments that the - user has access to. -

        -

        - This could be used, for example, to allow the user to perform a search for
        - http://baseurl/Observation?category=laboratory
        - and then receive results as though they had requested
        - http://baseurl/Observation?subject=Patient/123&category=laboratory. -

        -

        - An example of this interceptor follows: -

        - - - - - -
        - - - -
        diff --git a/src/site/xdoc/doc_server_tester.xml.vm b/src/site/xdoc/doc_server_tester.xml.vm deleted file mode 100644 index b8e0cb73891..00000000000 --- a/src/site/xdoc/doc_server_tester.xml.vm +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - Server Tester - James Agnew - - - - -
        - - - - -

        - HAPI FHIR includes a web UI that can be used to test your server implementation. - This UI is the same UI used on the http://fhirtest.uhn.ca - public server. -

        - -

        - The Tester is a - Maven WAR Overlay, - meaning that you create your own WAR project (which you would likely be doing anyway - to create your server) and the overlay drops a number of files into your project. -

        - - - -

        - These instructions assume that you have an exsiting web project - which uses Maven to build. The POM.xml should have a "packaging" - type of "war". -

        - -

        - Adding the overlay to your project is relatively simple. First, - add the "hapi-fhir-testpage-overlay" dependency to the dependencies - section of your POM.xml file. - - - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - ${project.version} - war - provided - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - ${project.version} - classes - provided - -]]> -

        - -

        - Then, add the following WAR plugin to the plugins section - of your POM.xml - - - - - - org.apache.maven.plugins - maven-war-plugin - - - - ca.uhn.hapi.fhir - hapi-fhir-testpage-overlay - - - - - -]]> -

        - -

        - Then, create a Java source file - called FhirTesterConfig.java - and copy in the following contents: -

        - - - - -

        - Note that the URL in the file above must be customized to point to - the FHIR endpoint your server will be deployed to. For example, if you - are naming your project "myfhir-1.0.war" and your endpoint in the WAR is - deployed to "/fhirbase/*" then you should put a URL similar to - http://localhost:8080/myfhir-1.0/fhirbase -

        - -

        - Next, create the following directory in your project - if it doesn't already exist:
        - src/main/webapp/WEB-INF -

        - -

        - In this directory you should open your web.xml file, or create - it if it doesn't exist. - This file is - required in order to deploy to a servlet container and you should create it if - it does not already exist. Place the following contents in that file, adjusting - the package on the FhirTesterConfig to match the - actual package in which you placed this file. -

        - - - spring - org.springframework.web.servlet.DispatcherServlet - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - ca.uhn.example.config.FhirTesterConfig - - 2 - - - spring - /tester/* - -]]> - -
        - - - -

        - The most important customization required is to - set the FHIR server base URL in the - hapi-fhir-tester-config.xml - configuration file created during the - previous step. -

        - -

        - Beyond this, the entire tester application is built - from a number of - Thymeleaf - template files, any of which can be replaced to - create your own look and feel. All of the templates - can be found in your built war (after running the Maven - build), or in the target directory's staging area, in - WEB-INF/templates. By placing a file - with the same path/name in your src/main/webapp/WEB-INF/templates - directory you can replace the built in template - with your own file. -

        - -
        - - - -

        - The "Restful Server Example" project contains a complete working - example of the FHIR Tester as a part of its configuration. You may - wish to browse its source to see how this works:
        - https://github.com/jamesagnew/hapi-fhir/tree/master/restful-server-example -

        - -
        - -
        - -
        - -

        - The testing UI uses its own client to talk to your FHIR server. In other words, there are no - special "hooks" which the tested uses to retrieve data from your server, it acts as an HTTP client - just like any other client. -

        - -

        - This does mean that if your server has any authorization requirements, you will need to configure the - tester UI to meet those requirements. For example, if your server has been configured to require - a HTTP Basic Auth header (e.g. Authorization: Basic VVNFUjpQQVNT) you need to - configure the tester UI to send those credentials across when it is acting as - a FHIR client. -

        - -

        - This is done by providing your own implementation of the ITestingUiClientFactory - interface. This interface takes in some details about the incoming request and produces - a client. -

        - -
        - - - -
        diff --git a/src/site/xdoc/doc_tags.xml b/src/site/xdoc/doc_tags.xml deleted file mode 100644 index 30c61ebed23..00000000000 --- a/src/site/xdoc/doc_tags.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - Resource Tags - James Agnew - - - - -
        - -

        - FHIR defines a useful framework for adding/updating/removing - Tags - against resource instances. A tag is a pair of URLs (a scheme, and a term) which - can optionally have a textual "label" as well. -

        - -

        - A specific resource instance's tags can be found in the resource - metadata - map, using the key of - ResourceMetadataKeyEnum.TAG_LIST. - The following example shows how to access the tags in a client - following a "read" operation: -

        - - - - - - - -

        - In a server implementation, you might want to - add tags to a resource being returned. This is done - by adding a TagList instance to the resource's - metadata, as shown in the example below. -

        - -

        - The server will then do the "right thing" with the tags. If the method - is for a search operation, the tags will be added to the category - element in the - returned bundle. If the method is for a read operation, the - tags will be added to the response "Category" headers. -

        - - - - - -
        - -
        - - - -
        diff --git a/src/site/xdoc/doc_tinder.xml.vm b/src/site/xdoc/doc_tinder.xml.vm deleted file mode 100644 index 19b71a0391f..00000000000 --- a/src/site/xdoc/doc_tinder.xml.vm +++ /dev/null @@ -1,100 +0,0 @@ - - - - - Tinder - James Agnew - - - - - - -
        - - - - -

        - According to the FHIR specification, any conformant server - must be able to export a Conformance statement, which - indicates all of the resource types and potential operations - that the server supports. -

        - -

        - HAPI provides a Maven plugin called "Tinder" which is able to automatically - generate a client based on that conformance statement. -

        - -
        - -
        - -

        - The following example shows a simple Maven plugin which - builds a client for the Health Intersections reference - server. -

        - -

        - Note that as of HAPI 0.8, you need to add a dependency to the - plugin containing the version of FHIR you are building custom - structures against. -

        - - - - ca.uhn.hapi.fhir - hapi-tinder-plugin - ${project.version} - - - generate-structures - - ca.uhn.hitest.HiTest - http://fhir.healthintersections.com.au/open - true - - - - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu - ${project.version} - - - -]]> - -

        - This example will create a class called - ca.uhn.hitest.HiTest which has - methods to invoke the various server operations. -

        - -

        - It is then possible to use this client as simply as: -

        - - - -
        - - - -
        diff --git a/src/site/xdoc/doc_upgrading.xml b/src/site/xdoc/doc_upgrading.xml deleted file mode 100644 index 34e47f38f8d..00000000000 --- a/src/site/xdoc/doc_upgrading.xml +++ /dev/null @@ -1,231 +0,0 @@ - - - - - Upgrade Guide - James Agnew - - - - -
        - -

        - Since the early days of FHIR, HL7.org has provided an - open source "Java Reference Implementation", which is an implementation - of a FHIR data model, parser, and client in Java. -

        -

        - HAPI was originally started as a separate Java implementation of FHIR, - with a fairly different focus: implementing servers with an easily - extendible data model. Over time though, the two Java implementations have - shown a fair bit of overlap, so at the 2014 - DevDays we decided - to try and harmonize the two libraries. -

        -

        - HAPI FHIR 1.1 begins the availability of a harmonized library. Note that - this version has not yet been formally released, but is currently available in - "snapshot" development builds. -

        -

        - For HAPI FHIR users: This integration will bring the ability to use powerful features - from the RI in your applications, such as the resource validator and the narrative - generator. -

        -

        - For RI users: This integration will bring the ability to use HAPI's client - and server frameworks in your application, as well as the ability to take advantage - of HAPI's code-first statically bound extension mechanism. -

        - - -

        - At this point, the RI integration consists of a new parallel set of - classes representing the FHIR data model. For example, in addition to the - Patient classes representing HAPI's DSTU1 and DSTU2 models there is now - a new Patient class representing the RI structure (which is also a DSTU2 - structure). -

        -

        - The reference implementation (RI) structures have been added as a new - maven dependency library called hapi-fhir-structures-hl7org-dstu2. See - the download page for information on the Maven - dependencies for this version of the structures. -

        -

        - A new interface has been added which serves as a master interface - for all resource classes: org.hl7.fhir.instance.model.api.IBaseResource. - All RI resource classes will be in the package org.hl7.fhir.instance.model, - as shown below. -

        - Structures - -

        - Datatypes will also be found inthe same package. Unlike HAPI datatype structures, - which all end with "Dt", the RI primitive structure names end with "Type" and the - RI composite structures have no suffix, as shown below. -

        - Structures - -
        - - - -

        - If you want to use the RI structures in your application, - you will need to use the "hapi-fhir-structures-hl7org-dstu2-[version].jar" - library. -

        - -

        - Using these structures is then similar to using other structures. -

        - - - - - - -
        - -
        - -
        - -

        - If you have an existing application built using a version of previous - version of HAPI FHIR, there is one change that may affect you. As shown above, - a new interface called IBaseResource has been introduced, and the - IResource interface extends from it. Many methods in the API which - previously returned IResource now return IBaseResource. -

        -

        - For these methods, you may cast the IBaseResource to - IResource. -

        - -
        - - - - - - -
        diff --git a/src/site/xdoc/doc_validation.xml b/src/site/xdoc/doc_validation.xml deleted file mode 100644 index 50bf5bcb91c..00000000000 --- a/src/site/xdoc/doc_validation.xml +++ /dev/null @@ -1,262 +0,0 @@ - - - - - - - Validation - James Agnew - - - -
        - -

        - HAPI supportes two types of validation, both of which are described in the - sections below. -

        -
          -
        • - Parser Validation - is validation at runtime during the parsing - of a resource. It can be used to catch input data that is impossible to - fit into the HAPI data model. For - example, it can be used to throw exceptions - or display error messages if a resource being parsed contains elements for which - there are no appropriate fields in a HAPI data structure. This is useful in order to ensure - that no data is being lost during parsing, but is less comprehensive than resource validation - against raw text data. -
        • -
        • - Resource Validation - is validation of the raw or parsed resource against - the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet) - as well as against custom profiles which have been developed. -
        • -
        - -
        - - -
        - -

        - Parser validation is controlled by calling - setParserErrorHandler(IParserErrorHandler) - on - either the FhirContext or on individual parser instances. This method - takes an - IParserErrorHandler - , which is a callback that - will be invoked any time a parse issue is detected. -

        -

        - There are two implementations of - IParserErrorHandler - worth - mentioning. You can also supply your own implementation if you want. -

        -
          -
        • - LenientErrorHandler - logs any errors but does not abort parsing. By default this handler is used, and it - logs errors at "warning" level. It can also be configured to silently - ignore issues. -
        • -
        • - StrictErrorHandler - throws a - DataFormatException - if any errors are detected. -
        • -
        - -

        - The following example shows how to configure a parser to use strict validation. -

        - - - - - -

        - You can also configure the error handler at the FhirContext level, which is useful - for clients. -

        - - - - - -

        - FhirContext level validators can also be useful on servers. -

        - - - - - -
        - - - -
        - -

        - HAPI provides a built-in and configurable mechanism for validating resources. - This mechanism is called the - Resource Validator. -

        -

        - The resource validator is an extendible and modular system, and you - can configure it in a number of ways in order to get the specific - type of validation you want to achieve. -

        - -

        - The validator can be manually invoked at any time by creating a - validator and configuring it with one or more - IValidatorModule - instances. -

        - - - - - - -
        - -
        - -

        - HAPI also supports validation against StructureDefinition - resources. This functionality uses the HL7 "InstanceValidator", which is able - to check a resource for conformance to FHIR profiles - (StructureDefinitions, ValueSets, and CodeSystems), - including validating fields, extensions, and codes for conformance to their given ValueSets. -

        -

        - StructureDefinition validation can be used to validate a resource against the - official structure definitions (produced by HL7) as well as against custom - definitions provided either by HL7 or by the user. -

        - -

        - The instance validator is experimental in the DSTU2 mode, but has become very stable - and full-featured in DSTU3+ mode. Use with caution when validating DSTU2 resources using - instance validator. -

        - - - -

        - To execute the validator, you simply create an instance of - FhirInstanceValidator - and register it to new validator, as shown in the example below. -

        - -

        - Note that the example below uses the official FHIR StructureDefintions and ValueSets - to validate the resource. It will not work unless you include the - hapi-fhir-validation-resources-[version].jar to your classpath. -

        - - - - - - -
        - - - -

        - The FhirInstanceValidator relies on the - IValidationSupport - interface to load StructureDefinitions, and validate codes. -

        -

        - By default, the - DefaultProfileValidationSupport - implementation is used. This implementation loads the FHIR profiles from the - validator resources JAR. If you want to use your own profiles, you may wish to - supply your own implementation. -

        - - - - - - -
        - -
        - -
        - -

        - FHIR resource definitions are distributed with a set of XML schema files (XSD) - as well as a set of XML Schematron (SCH) files. These two sets of files are - complimentary to each other, meaning that in order to claim compliance to the - FHIR specification, your resources must validate against both sets. -

        -

        - The two sets of files are included with HAPI, and it uses them to perform - validation. -

        - - - -

        - In order to use HAPI's Schematron support, a libaray called - Ph-Schematron - is used, so this library must be added to your classpath (or Maven POM file, Gradle - file, etc.) - Note that this library is specified as an optional dependency - by HAPI FHIR - so you need to explicitly include it if you want to use this - functionality. -

        -

        - See - Downloads - for more information on how - to add it. -

        -
        - - - -

        - To validate a resource instance, a new validator instance is requested - from the FHIR Context. This validator is then applied against - a specific resource - instance, as shown in the example below. -

        - - - - - -
        - - - -

        - The following example shows how to load a set of resources from files - on disk and validate each one. -

        - - - - - -
        - - -
        - - - -
        diff --git a/src/site/xdoc/docindex.xml b/src/site/xdoc/docindex.xml deleted file mode 100644 index 226d7e8b457..00000000000 --- a/src/site/xdoc/docindex.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - Documentation - James Agnew - - - - -
        - -

        - Welcome to HAPI FHIR! We hope that the documentation here will be - helpful to you. -

        - -
        - -

        The Data Model

        - - -

        RESTful Client

        - - -

        RESTful Server

        - - -

        Other Features

        - - - -

        JavaDocs

        - - -

        Source Cross Reference

        - - -
        - - - - diff --git a/src/site/xdoc/download.xml.vm b/src/site/xdoc/download.xml.vm deleted file mode 100644 index d15f8d6a12f..00000000000 --- a/src/site/xdoc/download.xml.vm +++ /dev/null @@ -1,655 +0,0 @@ - - - - - Download - - - - - - - -
        -

        - The following table shows the various versions of the HAPI FHIR library, and - the versions of the FHIR standard that they support. Note that support for - stable releases of FHIR are shown in - GREEN - and support for draft pre-release versions of FHIR are shown in - YELLOW. -

        -

        - Note also that after the release of the FHIR DSTU2 specification, the FHIR - standard itself stopped using the DSTUx naming scheme, in favour or naming - new releases STUx or simply Rx. Because HAPI FHIR already had draft support - for what was then called DSTU3 at this time, we did not update our naming - conventions until R4 in order to avoid breaking existing users' code. - From the perspective of a user of HAPI FHIR, consider the terms - DSTU3 / STU3 / R3 to be interchangeable. -

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        HAPI VersionMin JDKDSTU1DSTU2DSTU2.1DSTU3R4R5
        HAPI FHIR 1.1JDK60.0.820.5.0-5843
        HAPI FHIR 1.2JDK60.0.820.5.0-5843
        HAPI FHIR 1.3JDK60.0.821.0.2
        HAPI FHIR 1.4JDK60.0.821.0.21.3.0-7602
        HAPI FHIR 1.5JDK60.0.821.0.21.4.0-8138
        HAPI FHIR 1.6JDK60.0.821.0.21.4.0-8636
        HAPI FHIR 2.0JDK60.0.821.0.21.6.0-9663
        HAPI FHIR 2.1JDK60.0.821.0.21.7.0-10129
        HAPI FHIR 2.2JDK60.0.821.0.21.4.01.8.0-10528
        HAPI FHIR 2.3JDK60.0.821.0.21.4.01.9.0-11501
        HAPI FHIR 2.4JDK60.0.821.0.21.4.03.0.1
        HAPI FHIR 2.5JDK60.0.821.0.21.4.03.0.1
        HAPI FHIR 3.0.0JDK71.0.21.4.03.0.13.1.0-12370
        HAPI FHIR 3.1.0JDK71.0.21.4.03.0.13.1.0-12370
        HAPI FHIR 3.2.0JDK71.0.21.4.03.0.13.2.0-12917
        HAPI FHIR 3.3.0JDK71.0.21.4.03.0.13.2.0-13271
        HAPI FHIR 3.4.0JDK81.0.21.4.03.0.13.4.0-13732
        HAPI FHIR 3.5.0JDK81.0.21.4.03.0.13.4.0-13732
        HAPI FHIR 3.6.0JDK81.0.21.4.03.0.13.6.0-1202b2eed0f
        HAPI FHIR 3.7.0JDK81.0.21.4.03.0.14.0.0
        HAPI FHIR 3.8.0JDK81.0.21.4.03.0.14.0.0
        HAPI FHIR 4.0.0JDK81.0.21.4.03.0.14.0.04.1.0-e0e3caf9ba
        HAPI FHIR 4.1.0JDK81.0.21.4.03.0.24.0.14.1.0-1a7623d866
        - -
        - -
        -

        - The following table shows the modules that make up the HAPI - FHIR library. -

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Core Libraries
        hapi-fhir-base - This is the core HAPI FHIR library and is always required in order to use - the framework. It contains the context, parsers, and other support classes. -
        hapi-fhir-utilities - This is a support library containing various utility methods for working with - FHIR. It is always required in order to use the framework. -
        Structures
        hapi-fhir-structures-dstu - This module contains FHIR DSTU1 model classes. It was retired in HAPI FHIR 3.0.0. -
        hapi-fhir-structures-dstu2 - This module contains FHIR DSTU2 model classes. -
        hapi-fhir-structures-hl7org-dstu2 - This module contains alternate FHIR DSTU2 model classes. The HAPI FHIR and FHIR "Java Reference Implementation" - libraries were merged in 2015, and at the time there were two parallel sets of DSTU2 model - classes. This set more closely resembles the model classes for DSTU3+ where the other set - more closely resembles the DSTU1 model classes. The two DSTU2 model JARs are functionally - identital, but the various utility methods on the classes are somewhat different. -
        hapi-fhir-structures-dstu3 - This module contains FHIR DSTU3 model classes. -
        hapi-fhir-structures-r4 - This module contains FHIR R4 model classes. -
        Client Framework
        hapi-fhir-client - This module contains the core FHIR client framework, including an - HTTP implementation based on - Apache HttpClient. It is required in order - to use client functionality in HAPI. -
        hapi-fhir-client-okhttp - This module contains an alternate HTTP implementation based on - OKHTTP. -
        hapi-fhir-android - This module contains the Android HAPI FHIR framework, which is a FHIR - client framework which has been tailed specifically to run on Android. -
        Validation
        hapi-fhir-validation - This module contains the FHIR Profile Validator, which is used to - validate resource instances against FHIR Profiles (StructureDefinitions, - ValueSets, CodeSystems, etc.). -
        hapi-fhir-validation-resources-dstu2 - This module contains the StructureDefinitions, ValueSets, CodeSystems, Schemas, - and Schematrons for FHIR DSTU2 -
        hapi-fhir-validation-resources-dstu2.1 - This module contains the StructureDefinitions, ValueSets, CodeSystems, Schemas, - and Schematrons for FHIR DSTU2.1 -
        hapi-fhir-validation-resources-dstu3 - This module contains the StructureDefinitions, ValueSets, CodeSystems, Schemas, - and Schematrons for FHIR DSTU3 -
        hapi-fhir-validation-resources-r4 - This module contains the StructureDefinitions, ValueSets, CodeSystems, Schemas, - and Schematrons for FHIR R4 -
        Server
        hapi-fhir-server - This module contains the HAPI FHIR Server framework, which can be used to - develop FHIR compliant servers against your own data storage layer. -
        hapi-fhir-jpaserver-base - This module contains the HAPI FHIR "JPA Server", which is a complete - FHIR server solution including a database and implementations of many - advanced FHIR server features. -
        hapi-fhir-testpage-overlay - This module contains the web based "testpage overlay", which is the - UI that powers our - Public Demo Server - and can also be added to your applications. -
        -
        - -
        -

        - If you are developing applications in Java, the easiest way to use HAPI is - to use a build system which handles dependency management automatically. The - two most common such systems are - Apache Maven and - Gradle. These systems will automatically - download "dependency" libraries and add them to your classpath. - If you are not using one of these systems, you can still manually download - the latest release of HAPI by looking in the - GitHub Release Section. -

        - - -

        - FHIR is a fast moving specification, and there is a lot of ongoing work - in HAPI as well. While we regularly put out new releases, there may be - times when you want to try out the latest unreleased version. You can ususally - look at the source of the - changes report - to get a sense of what has changed in the next unreleased version. -

        -

        - See using snapshot builds below to find out - how to get these builds. -

        -
        - -
        - -
        -

        - To use HAPI in your application, at a minimum you need to include the HAPI-FHIR core - JAR hapi-fhir-base-[version].jar, as well as at least one "structures" JAR. - The structures JAR contains classes with the resource and datatype definitions for a given - version of FHIR. -

        - - - -

        - HAPI also has a hapi-fhir-structures-dstu2-[version].jar, which - contains the latest versions of the releases. You can include this JAR on - your classpath if you want to use resources that were created or updated by - HL7 after the DSTU1 release. Be warned though that using these resources - can lead to incompatibility between your application and other applications - if those applications are designed to be compliant with FHIR DSTU1. -

        - -

        - If you are using Maven, add the following dependency to include DSTU2 resources. - Note that if you do not need to support DSTU1 resources, you do not need to - include the "hapi-fhir-structures-dstu" artifact. -

        - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu2 - ${project.version} -]]> - -
        - - - -

        - To use the HL7.org reference implementation structures - (see the DSTU2 page for more information), - use the following dependency. -

        - - ca.uhn.hapi.fhir - hapi-fhir-structures-hl7org-dstu2 - ${project.version} -]]> - -

        - If you want to use HAPI's - StructureDefinition validation - you will also need to include the hapi-fhir-validation-resources-dstu2-[version].jar: -

        - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2 - ${project.version} -]]> - -
        - -
        - -
        -

        - If you are using Gradle, you may use the following dependencies. Note that if - you are doing Android development, you may want to use our - Android build instead. -

        -

        - DSTU1: -

        - -

        - DSTU2 (HAPI): -

        - -

        - DSTU2 (RI): -

        - -
        - -
        - -

        - Snapshot builds of HAPI are pre-release builds which can contain - fixes and new features not yet released in a formal release. To use - snapshot builds of HAPI you may need to add a reference to the OSS snapshot - repository to your project build file. -

        -

        - Using Maven: -

        - - - - oss-snapshots - - true - - https://oss.sonatype.org/content/repositories/snapshots/ - -]]> - -

        - Using Gradle: -

        - - -
        - -
        - -

        - The HAPI-FHIR library depends on other libraries to provide specific functionality. - Some of those libraries are listed here: -

        - - - -

        - HAPI requires SLF4j for logging support, and it is recommended to include - an underlying logging framework such as Logback. See the - logging documentation for - more information. -

        - -
        - - - -

        - XML processing (for resource marshalling and unmarshalling) uses the - Java StAX API, which is a fast and efficient API for XML processing. - HAPI bundles (for release archives) and depends on (for Maven builds) - the Woodstox library, which - is a good implementation of StAX. -

        -

        - Upon starting up, HAPI will emit a log line indicating which StAX implementation - is being used, e.g: -

        - 08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML procesing will use StAX implementation 'Woodstox XML-processor' version '4.4.0' -

        - Although most testing is done using the Woodstox implementation of - StAX, it is not required and HAPI should work correctly with any - compliant implementation of StAX. -

        -

        - You can force Woodstox in an environment where multiple StAX libraries are - present by setting the following system properties: -

        - System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); -System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); -System.setProperty("javax.xml.stream.XMLEventFactory", "com.ctc.wstx.stax.WstxEventFactory"); - -
        - - - -

        - If you are using the - Schematron Validatioon - module, you will also need to include the Ph-Schematron library on your - classpath. (Note that prior to HAPI FHIR 3.4.0 we used Phloc-Schamtron - instead, but that lirary has been discontinued) -

        -

        - If you are using Maven, this library is not added by default (it is - marked as an optional dependency) since not all applications need Schematron - support. As a result you will need to manually add the following - dependencies to your project POM.xml -

        - - com.helger - ph-schematron - ${ph_schematron_version} - - - com.helger - ph-commons - ${ph_commons_version} -]]> -
        - - -
        - - - -
        diff --git a/src/site/xdoc/hacking_hapi_fhir.xml b/src/site/xdoc/hacking_hapi_fhir.xml deleted file mode 100644 index 684507f5738..00000000000 --- a/src/site/xdoc/hacking_hapi_fhir.xml +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - Hacking HAPI FHIR - - - - -
        - -

        - This page contains useful information about how to get started in developing - HAPI FHIR itself. -

        - -
        - -
        - -

        - The HAPI FHIR Codebase - has a number of subprojects. You will typically need to interact with several - of them in order to develop HAPI, but you generally don't need all of them. -

        - -

        - The following is a list of key subprojects you might open in your IDE: -

        -
          -
        • - hapi-fhir-base: - This is the core library, containing the parsers, client/server frameworks, and many other features. Note - that this module does not contain any model classes (e.g. the Patient model class) as these are found - in "structures" projects below. -
        • -
        • - hapi-fhir-structures-[version]: - There are several structures projects (e.g. hapi-fhir-structures-dstu2), each of - which contains model classes for a specific version of FHIR.It is generally a good idea - to open all of these in your IDE. -
        • -
        • - hapi-fhir-validation-resources-[version]: - There are several validation resources projects (e.g. hapi-fhir-validation-resources-dstu2), each of - which contains text resources for the given version. These resources are text resources produced - as a part of the FHIR specification build (e.g. StructureDefinitions, ValueSets, etc.) -
        • -
        • - hapi-fhir-jpaserver-base: - This module contains the JPA server. -
        • -
        - -
        - -
        -

        - Build Status -

        -

        - The best way to grab our sources is with Git. Grab the repository URL - from our GitHub page. - We try our best to ensure that the sources are always left in a buildable state. Check - Travis (see the image/link on the right) to see if the sources currently build. -

        -
        - -
        -

        - HAPI is built primary using - Apache Maven. Even if you are using an IDE, - you should start by performing a command line build before trying to get - everything working in an IDE. -

        -

        - Execute the build with the following command:
        - mvn install -

        -

        - Note that this complete build takes a long time because of all of the unit tests - being executed. At the end you should expect to see a screen resembling:
        -

        []INFO] ------------------------------------------------------------------------
        -[INFO] Reactor Summary:
        -[INFO] 
        -[INFO] HAPI-FHIR .......................................... SUCCESS [  4.456 s]
        -[INFO] HAPI FHIR - Deployable Artifact Parent POM ......... SUCCESS [  2.841 s]
        -[INFO] HAPI FHIR - Core Library ........................... SUCCESS [01:00 min]
        -[INFO] HAPI Tinder Plugin ................................. SUCCESS [ 19.259 s]
        -[INFO] HAPI FHIR Structures - DSTU1 (FHIR v0.80) .......... SUCCESS [01:40 min]
        -[INFO] HAPI FHIR Structures - DSTU2 (FHIR v1.0.0) ......... SUCCESS [01:14 min]
        -[INFO] HAPI FHIR Structures - DSTU3 ....................... SUCCESS [02:11 min]
        -.... some lines removed .....
        -[INFO] ------------------------------------------------------------------------
        -[INFO] BUILD SUCCESS
        -[INFO] ------------------------------------------------------------------------
        -[INFO] Total time: 20:45 min
        -[INFO] Finished at: 2016-02-27T15:05:35+00:00
        -

        - - - -

        - If the build fails to execute successfully, try the following: -

        -
          -
        • - The first thing to try is always a fresh clean build when things aren't working:
          -
          mvn clean install
          -
        • -
        • - If you are trying to build a submodule (e.g. hapi-fhir-jpaserver-example), - try building the root project first. Especially when building from the Git master, - often times there will be dependencies that require a fresh complete build (note that this is - not generally an issue when building from a release version)
          -
          -
        • -
        • - If the build fails with memory issues (or mysteriously dies during unit tests), - your build environment may be running out of memory. By default, the HAPI build executes - unit tests in multiple parallel JVMs in order to save time. This can consume a lot of RAM - and sometimes causes issues. Try executing with the following command to disable - this behaviour:
          -
          mvn -P ALLMODULES,NOPARALLEL install
          -
        • -
        • - If you figure something else out, please let us know so that we can add it - to this list! -
        • -
        - -
        - -
        - -
        -

        - This section shows how to import HAPI into Eclipse. There is no requirement - to use Eclipse (IntelliJ/IDEA and Netbeans are both fine!) so feel free to - skip this section. -

        -

        - Maven Import
        - Import the HAPI projects as Maven Modules by selecing - File -> Import... from the File menu. Then select - Existing Module Projects as shown below. -

        -

        -

        - Select the Projects
        - Next, browse to the directory where you checked out the HAPI FHIR sources. - You might want to select only the projects you are interested in editing, - in order to keep Eclipse's memory use down. You can always come back and - import more later. -

        - - - - -

        - When importing the HAPI projects into Eclipse, sometimes Eclipse - will fail to correctly import libraries. If you import a module - into Eclipse and it fails to compile with many errors relating to - packages other than HAPI's, the following steps will fix this: -

        -
          -
        • Delete the project from your Eclipse workspace
        • -
        • - On the local filesystem, delete the files .project - and .classpath, and the directory .settings - from each module you want to open. -
        • -
        • - Import each module again using the instructions above -
        • -
        - -
        - -
        - - -
        diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml deleted file mode 100644 index 9bd466af806..00000000000 --- a/src/site/xdoc/index.xml +++ /dev/null @@ -1,1437 +0,0 @@ - - - - - The Open Source HL7 API for Java - James Agnew - - - - - -
        -

        - -
        - -
        - -
        - Build Status -
        - Coverage Status -
        - Maven Central -
        - Apache 2.0 Licensed -

        - -

        - This is the homepage for the HAPI-FHIR library. We are developing - an open-source implementation of the FHIR specification in Java. - FHIR - (Fast Healthcare Interoperability Resources) - is a specification for exchanging healthcare data in a modern - and developer friendly way. -

        - -

        - Note that this is the home for the FHIR version of HAPI. If you are - looking for HL7 v2 support, click here. -

        - - - -

        - A public test server is now operating at - http://hapi.fhir.org. - This server is built entirely using components of HAPI-FHIR - and demonstrates all of its capabilities. This server is also - entirely open source. You can host your own copy by - following instructions on our - JPA Server - documentation. -

        - -
        - - - -

        - Commercial support for HAPI FHIR is available through - Smile CDR. -

        - -
        - -
        - -
        -

        - November 13, 2019 - HAPI FHIR 4.1.0 (Jitterbug) - - It's time for another release of HAPI FHIR! -

        -

        - This release brings some good stuff, including: -

        -
          -
        • - Structures JARs have been updated to incorporate the latest technical corrections. - DSTU3 structures are upgraded to FHIR 3.0.2, R4 structures are upgraded to - FHIR 4.0.1, and R5 draft structures are upgraded to the October 2019 draft revision. -
        • -
        • - ValueSets are now automatically pre-expanded by the JPA server into - a dedicated set of database tables. This "precalculated expansion" is used - to provide much better performance for validation and expanion operations, and - introduced the ability to successfully expand very large ValueSets - such as the LOINC implicit (all codes) valueset. -
        • -
        • - Support for the FHIR Bulk Export specification has been added. We are now - working on adding support for Bulk Import! -
        • -
        • - First-order support for ElasticSearch as a full-text and terminology service - backend implementation. At this time, both raw Lucene and ElasticSearch are - supported (this may change in the future but we do not have any current - plans to deprecate Lucene). -
        • -
        • - Live Terminology Service operations for terminology file maintenance based on - delta files has been added. -
        • -
        • - Binary resources and Media/DocumentReference instances with binary attachments - stored in the FHIR repository can now take advantage of - externalized binary storage for the binary content when that feature is enabled. - This allows much better scalability of repositories containing large amounts - of binary content (e.g. document repositories). -
        • -
        -

        - As always, see the - changelog for a full list - of changes. -

        -

        - Thanks to everyone who contributed to this release! -

        -

        - Also, as a reminder, if you have not already filled out our annual - user survey, please take a moment to do so. Access the survey here: - http://bit.ly/33HO4cs (note that this URL was originally posted incorrectly. It is now fixed) -

        -

        - - James Agnew -

        -

        - - - -

        - September 3, 2019 - Community Survey and HAPI FHIR 4.0.1 - - It is time for us to do another HAPI FHIR community survey. The survey is a not-quite-annual - tradition that helps us to set priority for the coming year and get a pulse on how people - are using HAPI FHIR. -

        -

        - We would very much appreciate if everyone could take a few minutes to fill it out. The - survey is short (2 pages / 5 mins) so it shouldn't be much of a burden. -

        -

        - Access the survey here: - http://bit.ly/33HO4cs (note that this URL was originally posted incorrectly. It is now fixed) -

        -

        - In addition, a new HAPI FHIR release (4.0.1) has been uploaded to the - Maven Central repos. - This release contains no new or updated functionality, but addressed a dependency - version that was left incorrectly requiring a SNAPSHOT maven build of the - org.hl7.fhir.utilities module. Users who are successfully using HAPI FHIR 4.0.0 - do not need to upgrade, but any users who were blocked from upgrading due to - snapshot dependency issues are advised to upgrade immediately. -

        - - - -

        - August 14, 2019 - HAPI FHIR 4.0.0 (Igloo) Released - - The next release of HAPI has now been uploaded to the Maven repos and - GitHub's releases section. -

        -

        - This release features a number of significant performance improvements, - and has some notable changes: -

        -
          -
        • - A new consent framework called ConsentInterceptor that can be used to apply local consent directives and policies, and potentially filter or mask data has been added. -
        • -
        • - Initial support for draft FHIR R5 resources has been added. -
        • -
        • - Support for GraphQL and the _filter search parameter has been added. -
        • -
        • - The ability to perform cascading deletes has been added. -
        • -
        -

        - As always, see the - changelog for a full list - of changes. You can also watch the release webinar! -

        -

        - Thanks to everyone who contributed to this release! -

        -

        - - James Agnew -

        -

        - - - - - - - - - - - - - - - - - - - -
        - -
        -

        - HAPI FHIR is a simple-but-powerful library for adding FHIR messaging to your application. It - is pure Java (1.6+ compatible), and licensed under the business-friendly Apache Software - License, version 2.0. -

        - - -

        - HAPI is designed with one main intent: providing a flexible way of adding - FHIR capability to applications. We at University Health Network - developed HAPI-FHIR to allow us to - build up our own unified FHIR RESTful server which exposes data backed by - a number of systems and repositories, so it is designed to be flexible - above all else. -

        -

        - The library is designed to support several main usage patterns: -
        - -

        -
        - - - -

        - The HAPI API is designed to allow interaction with - FHIR model objects using a convenient - Fluent Interface. -

        - - -
        - - -

        - Both XML and JSON encoding are suported natively using a simple API - to pick between them. XML support is built on top of the lightning-fast - STaX/JSR 173 - API, and JSON support is provided using Google Gson. -

        - - - -
        - - -

        - Creating clients is simple and uses an annotation based format - that will be familiar to users of JAX-WS. -

        - - findPatientsByIdentifier(@RequiredParam(name="identifier") IdentifierDt theIdentifier); - - /** A FHIR create */ - @Create - public MethodOutcome createPatient(@ResourceParam Patient thePatient); -}]]> - -

        - Using this client is as simple as: -

        - - clients = client.findPatientsByIdentifier(searchParam);]]> - -
        -
        - - - -
        diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 5c69d2bfd64..f8341fb7b9a 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index f3a5689802a..c1332424852 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index 56809aad658..dbe808d05ec 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT ../../pom.xml