diff --git a/.travis.yml b/.travis.yml index bec09c5d1cd..5d6fca80e68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,5 @@ before_script: script: # - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report # - mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report - - mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report + - mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE_JDK8 clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report diff --git a/appveyor.yml b/appveyor.yml index afb21a48371..a08ed7500ff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,4 +4,4 @@ cache: - C:\maven\ - C:\Users\appveyor\.m2\repository build_script: - - cmd: mvn -P MINPARALLEL,ALLMODULES install + - cmd: mvn -P MINPARALLEL,ALLMODULES,ERRORPRONE_JDK8 install diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java index fafd274cdcb..6b9c2a09fa4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.context; * 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. @@ -40,16 +40,14 @@ public enum FhirVersionEnum { DSTU2_1("org.hl7.fhir.dstu2016may.hapi.ctx.FhirDstu2_1", null, true, new Version("1.4.0")), - DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()), - - R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()), - - ; + DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()), + + R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),; private final FhirVersionEnum myEquivalent; private final boolean myIsRi; - private volatile Boolean myPresentOnClasspath; private final String myVersionClass; + private volatile Boolean myPresentOnClasspath; private volatile IFhirVersion myVersionImplementation; private String myFhirVersionString; @@ -82,21 +80,6 @@ public enum FhirVersionEnum { return ordinal() >= theVersion.ordinal(); } - /** - * Returns the {@link FhirVersionEnum} which corresponds to a specific version of - * FHIR. Partial version strings (e.g. "3.0") are acceptable. - * - * @return Returns null if no version exists matching the given string - */ - public static FhirVersionEnum forVersionString(String theVersionString) { - for (FhirVersionEnum next : values()) { - if (next.getFhirVersionString().startsWith(theVersionString)) { - return next; - } - } - return null; - } - public boolean isEquivalentTo(FhirVersionEnum theVersion) { if (this.equals(theVersion)) { return true; @@ -139,15 +122,50 @@ public enum FhirVersionEnum { return myIsRi; } + public FhirContext newContext() { + switch (this) { + case DSTU2: + return FhirContext.forDstu2(); + case DSTU2_HL7ORG: + return FhirContext.forDstu2Hl7Org(); + case DSTU2_1: + return FhirContext.forDstu2_1(); + case DSTU3: + return FhirContext.forDstu3(); + case R4: + return FhirContext.forR4(); + } + throw new IllegalStateException("Unknown version: " + this); // should not happen + } + + /** + * Returns the {@link FhirVersionEnum} which corresponds to a specific version of + * FHIR. Partial version strings (e.g. "3.0") are acceptable. + * + * @return Returns null if no version exists matching the given string + */ + public static FhirVersionEnum forVersionString(String theVersionString) { + for (FhirVersionEnum next : values()) { + if (next.getFhirVersionString().startsWith(theVersionString)) { + return next; + } + } + return null; + } + + private interface IVersionProvider { + String provideVersion(); + } + private static class Version implements IVersionProvider { + private String myVersion; + public Version(String theVersion) { super(); myVersion = theVersion; } - private String myVersion; - @Override public String provideVersion() { return myVersion; @@ -155,17 +173,14 @@ public enum FhirVersionEnum { } - private interface IVersionProvider { - String provideVersion(); - } - /** * This class attempts to read the FHIR version from the actual model * classes in order to supply an accurate version string even over time - * */ private static class Dstu3Version implements IVersionProvider { + private String myVersion; + public Dstu3Version() { try { Class c = Class.forName("org.hl7.fhir.dstu3.model.Constants"); @@ -175,8 +190,6 @@ public enum FhirVersionEnum { } } - private String myVersion; - @Override public String provideVersion() { return myVersion; @@ -186,6 +199,8 @@ public enum FhirVersionEnum { private static class R4Version implements IVersionProvider { + private String myVersion; + public R4Version() { try { Class c = Class.forName("org.hl7.fhir.r4.model.Constants"); @@ -195,8 +210,6 @@ public enum FhirVersionEnum { } } - private String myVersion; - @Override public String provideVersion() { return myVersion; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseAndListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseAndListParam.java index 0cf99958860..934cdaa0423 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseAndListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseAndListParam.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param; * 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. @@ -20,25 +20,32 @@ package ca.uhn.fhir.rest.param; * #L% */ -import java.util.ArrayList; -import java.util.List; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import java.util.ArrayList; +import java.util.List; + public abstract class BaseAndListParam> implements IQueryParameterAnd { - private List myValues=new ArrayList(); - + private List myValues = new ArrayList<>(); + + public abstract BaseAndListParam addAnd(T theValue); + public BaseAndListParam addValue(T theValue) { myValues.add(theValue); return this; } - - public abstract BaseAndListParam addAnd(T theValue); + + @Override + public List getValuesAsQueryTokens() { + return myValues; + } + + abstract T newInstance(); @Override public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List theParameters) throws InvalidRequestException { @@ -50,11 +57,9 @@ public abstract class BaseAndListParam> implement } } - abstract T newInstance(); - @Override - public List getValuesAsQueryTokens() { - return myValues; + public String toString() { + return myValues.toString(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java index 96695ae26d9..8e64beceaeb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseOrListParam.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param; * 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. @@ -20,23 +20,35 @@ package ca.uhn.fhir.rest.param; * #L% */ -import java.util.ArrayList; -import java.util.List; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.QualifiedParamList; +import java.util.ArrayList; +import java.util.List; + abstract class BaseOrListParam, PT extends IQueryParameterType> implements IQueryParameterOr { - private List myList=new ArrayList(); + private List myList = new ArrayList<>(); + + @SuppressWarnings("unchecked") + public MT add(PT theParameter) { + if (theParameter != null) { + myList.add(theParameter); + } + return (MT) this; + } + + public abstract MT addOr(PT theParameter); + + @Override + public List getValuesAsQueryTokens() { + return myList; + } + + abstract PT newInstance(); -// public void addToken(T theParam) { -// Validate.notNull(theParam,"Param can not be null"); -// myList.add(theParam); -// } - @Override public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { myList.clear(); @@ -47,21 +59,9 @@ abstract class BaseOrListParam, PT extends IQue } } - abstract PT newInstance(); - - public abstract MT addOr(PT theParameter); - - @SuppressWarnings("unchecked") - public MT add(PT theParameter) { - if (theParameter != null) { - myList.add(theParameter); - } - return (MT) this; - } - @Override - public List getValuesAsQueryTokens() { - return myList; + public String toString() { + return myList.toString(); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java index 17f572646cd..153cb035ff7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java @@ -69,14 +69,12 @@ public class TokenOrListParam extends BaseOrListParam getListAsCodings() { diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index b0e1171a610..10348db39f7 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -191,6 +191,23 @@ phloc-commons + + + javax.xml.bind + jaxb-api + + + com.sun.xml.bind + jaxb-core + + + com.sun.xml.bind + jaxb-impl + + org.fusesource.jansi jansi diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 3be9a274a5d..d57d06bca18 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -32,6 +32,12 @@ provided + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu2 + 3.3.0-SNAPSHOT + true + ca.uhn.hapi.fhir hapi-fhir-structures-hl7org-dstu2 @@ -70,6 +76,11 @@ + + ch.qos.logback + logback-classic + test + ca.uhn.hapi.fhir hapi-fhir-client diff --git a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptor.java b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptor.java index b1e5a0b532b..c26f4118c30 100644 --- a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptor.java +++ b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptor.java @@ -21,39 +21,67 @@ package ca.uhn.hapi.converters.server; */ import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServerUtils; +import ca.uhn.fhir.rest.api.server.ResponseDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; -import org.hl7.fhir.convertors.VersionConvertor_30_40; +import org.hl7.fhir.convertors.*; +import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Collections; -import java.util.Set; import java.util.StringTokenizer; -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; +/** + * This is an experimental interceptor! Use with caution as + * behaviour may change or be removed in a future version of + * FHIR. + *

+ * This interceptor partially implements the proposed + * Versioned API features. + *

+ */ public class VersionedApiConverterInterceptor extends InterceptorAdapter { - private VersionConvertor_30_40 myVersionConvertor_30_40 = new VersionConvertor_30_40(); + private final FhirContext myCtxDstu2; + private final FhirContext myCtxDstu2Hl7Org; + private VersionConvertor_30_40 myVersionConvertor_30_40; + private VersionConvertor_10_40 myVersionConvertor_10_40; + private VersionConvertor_10_30 myVersionConvertor_10_30; + + public VersionedApiConverterInterceptor() { + myVersionConvertor_30_40 = new VersionConvertor_30_40(); + VersionConvertorAdvisor40 advisor40 = new NullVersionConverterAdvisor40(); + myVersionConvertor_10_40 = new VersionConvertor_10_40(advisor40); + VersionConvertorAdvisor30 advisor30 = new NullVersionConverterAdvisor30(); + myVersionConvertor_10_30 = new VersionConvertor_10_30(advisor30); + + myCtxDstu2 = FhirContext.forDstu2(); + myCtxDstu2Hl7Org = FhirContext.forDstu2Hl7Org(); + } @Override - public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { - String accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT)); + public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { + String[] formatParams = theRequestDetails.getParameters().get(Constants.PARAM_FORMAT); + String accept = null; + if (formatParams != null && formatParams.length > 0) { + accept = formatParams[0]; + } + if (isBlank(accept)) { + accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT)); + } StringTokenizer tok = new StringTokenizer(accept, ";"); String wantVersionString = null; while (tok.hasMoreTokens()) { String next = tok.nextToken().trim(); - if (next.startsWith("fhir-version=")) { - wantVersionString = next.substring("fhir-version=".length()).trim(); + if (next.startsWith("fhirVersion=")) { + wantVersionString = next.substring("fhirVersion=".length()).trim(); break; } } @@ -62,31 +90,48 @@ public class VersionedApiConverterInterceptor extends InterceptorAdapter { if (isNotBlank(wantVersionString)) { wantVersion = FhirVersionEnum.forVersionString(wantVersionString); } - FhirVersionEnum haveVersion = theResponseObject.getStructureFhirVersionEnum(); + + IBaseResource responseResource = theResponseDetails.getResponseResource(); + FhirVersionEnum haveVersion = responseResource.getStructureFhirVersionEnum(); IBaseResource converted = null; try { if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU3) { - converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theResponseObject); + converted = myVersionConvertor_30_40.convertResource(toDstu3(responseResource)); } else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) { - converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject); - } else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) { - converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject); + converted = myVersionConvertor_30_40.convertResource(toR4(responseResource)); + } else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.R4) { + converted = myVersionConvertor_10_40.convertResource(toR4(responseResource)); + } else if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU2) { + converted = myVersionConvertor_10_40.convertResource(toDstu2(responseResource)); + } else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.DSTU3) { + converted = myVersionConvertor_10_30.convertResource(toDstu3(responseResource)); + } else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.DSTU2) { + converted = myVersionConvertor_10_30.convertResource(toDstu2(responseResource)); } } catch (FHIRException e) { throw new InternalErrorException(e); } if (converted != null) { - Set objects = Collections.emptySet(); - try { - RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), converted, objects, 200, "OK", false, false, theRequestDetails, null, null); - return false; - } catch (IOException e) { - throw new InternalErrorException(e); - } + theResponseDetails.setResponseResource(converted); } return true; } + + private org.hl7.fhir.instance.model.Resource toDstu2(IBaseResource theResponseResource) { + if (theResponseResource instanceof IResource) { + return (org.hl7.fhir.instance.model.Resource) myCtxDstu2Hl7Org.newJsonParser().parseResource(myCtxDstu2.newJsonParser().encodeResourceToString(theResponseResource)); + } + return (org.hl7.fhir.instance.model.Resource) theResponseResource; + } + + private Resource toDstu3(IBaseResource theResponseResource) { + return (Resource) theResponseResource; + } + + private org.hl7.fhir.r4.model.Resource toR4(IBaseResource theResponseResource) { + return (org.hl7.fhir.r4.model.Resource) theResponseResource; + } } diff --git a/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/NullVersionConverterAdvisor40.java b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/NullVersionConverterAdvisor40.java new file mode 100644 index 00000000000..22219b57aae --- /dev/null +++ b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/NullVersionConverterAdvisor40.java @@ -0,0 +1,56 @@ +package org.hl7.fhir.convertors; + +/* + * #%L + * HAPI FHIR - Converter + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.Resource; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ValueSet; + +public class NullVersionConverterAdvisor40 implements VersionConvertorAdvisor40 { + + @Override + public Resource convertR2(org.hl7.fhir.r4.model.Resource resource) throws FHIRException { + return null; + } + + @Override + public org.hl7.fhir.dstu3.model.Resource convertR3(org.hl7.fhir.r4.model.Resource resource) throws FHIRException { + return null; + } + + @Override + public CodeSystem getCodeSystem(ValueSet theSrc) { + return null; + } + + @Override + public void handleCodeSystem(CodeSystem theTgtcs, ValueSet theSource) { + //nothing + } + + @Override + public boolean ignoreEntry(BundleEntryComponent theSrc) { + return false; + } + +} diff --git a/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/SearchDstu3Test.java b/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/SearchDstu3Test.java deleted file mode 100644 index ed659016d8a..00000000000 --- a/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/SearchDstu3Test.java +++ /dev/null @@ -1,276 +0,0 @@ -package ca.uhn.hapi.converters.server; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.annotation.RequiredParam; -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.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.gclient.StringClientParam; -import ca.uhn.fhir.rest.param.TokenAndListParam; -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; -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.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.util.UrlUtil; -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; -import org.apache.http.impl.client.HttpClientBuilder; -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.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.HumanName; -import org.hl7.fhir.dstu3.model.OperationOutcome; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.instance.model.api.IBaseResource; -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; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; - -public class SearchDstu3Test { - - private static CloseableHttpClient ourClient; - private static FhirContext ourCtx = FhirContext.forDstu3(); - private static TokenAndListParam ourIdentifiers; - private static String ourLastMethod; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDstu3Test.class); - private static int ourPort; - - private static Server ourServer; - - @Before - public void before() { - ourLastMethod = null; - ourIdentifiers = null; - } - - @Test - public void testSearchNormal() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar"); - CloseableHttpResponse status = ourClient.execute(httpGet); - try { - String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(responseContent); - assertEquals(200, status.getStatusLine().getStatusCode()); - - assertEquals("search", ourLastMethod); - - assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem()); - assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); - } finally { - IOUtils.closeQuietly(status.getEntity().getContent()); - } - - } - - @Test - public void testSearchWithInvalidChain() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar"); - CloseableHttpResponse status = ourClient.execute(httpGet); - try { - String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(responseContent); - assertEquals(400, status.getStatusLine().getStatusCode()); - - OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent); - assertEquals( - "Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)", - oo.getIssueFirstRep().getDiagnostics()); - } finally { - IOUtils.closeQuietly(status.getEntity().getContent()); - } - - } - - - @Test - public void testPagingPreservesEncodingJson() throws Exception { - HttpGet httpGet; - String linkNext; - Bundle bundle; - - // Initial search - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json"); - bundle = executeAndReturnLinkNext(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); - 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); - 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); - linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=json")); - - } - - @Test - public void testPagingPreservesEncodingApplicationJsonFhir() throws Exception { - HttpGet httpGet; - String linkNext; - Bundle bundle; - - // Initial search - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW); - bundle = executeAndReturnLinkNext(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); - 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); - 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); - linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW))); - - } - - - private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException { - CloseableHttpResponse status = ourClient.execute(httpGet); - Bundle bundle; - try { - String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(responseContent); - assertEquals(200, status.getStatusLine().getStatusCode()); - EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim()); - assertEquals(theExpectEncoding, ct); - bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent); - assertEquals(10, bundle.getEntry().size()); - String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertNotNull(linkNext); - } finally { - IOUtils.closeQuietly(status.getEntity().getContent()); - } - return bundle; - } - - - @Test - public void testSearchWithPostAndInvalidParameters() { - IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort); - LoggingInterceptor interceptor = new LoggingInterceptor(); - interceptor.setLogRequestSummary(true); - interceptor.setLogRequestBody(true); - interceptor.setLogRequestHeaders(false); - interceptor.setLogResponseBody(false); - interceptor.setLogResponseHeaders(false); - interceptor.setLogResponseSummary(false); - client.registerInterceptor(interceptor); - try { - client - .search() - .forResource(Patient.class) - .where(new StringClientParam("foo").matches().value("bar")) - .prettyPrint() - .usingStyle(SearchStyleEnum.POST) - .returnBundle(Bundle.class) - .encodedJson() - .execute(); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]")); - } - - } - - @AfterClass - public static void afterClassClearContext() throws Exception { - ourServer.stop(); - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void beforeClass() throws Exception { - ourPort = PortUtil.findFreePort(); - ourServer = new Server(ourPort); - - DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); - - ServletHandler proxyHandler = new ServletHandler(); - RestfulServer servlet = new RestfulServer(ourCtx); - servlet.setDefaultResponseEncoding(EncodingEnum.JSON); - servlet.setPagingProvider(new FifoMemoryPagingProvider(10)); - - servlet.setResourceProviders(patientProvider); - ServletHolder servletHolder = new ServletHolder(servlet); - proxyHandler.addServletWithMapping(servletHolder, "/*"); - ourServer.setHandler(proxyHandler); - ourServer.start(); - - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); - HttpClientBuilder builder = HttpClientBuilder.create(); - builder.setConnectionManager(connectionManager); - ourClient = builder.build(); - - } - - public static class DummyPatientResourceProvider implements IResourceProvider { - - @Override - public Class getResourceType() { - return Patient.class; - } - - @SuppressWarnings("rawtypes") - @Search() - public List search( - @RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { - ourLastMethod = "search"; - ourIdentifiers = theIdentifiers; - ArrayList retVal = new ArrayList(); - - for (int i = 0; i < 200; i++) { - Patient patient = new Patient(); - patient.addName(new HumanName().setFamily("FAMILY")); - patient.getIdElement().setValue("Patient/" + i); - retVal.add(patient); - } - return retVal; - } - - } - -} diff --git a/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptorR4Test.java b/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptorR4Test.java new file mode 100644 index 00000000000..c6c357b2bad --- /dev/null +++ b/hapi-fhir-converter/src/test/java/ca/uhn/hapi/converters/server/VersionedApiConverterInterceptorR4Test.java @@ -0,0 +1,131 @@ +package ca.uhn.hapi.converters.server; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +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.apache.http.impl.client.HttpClientBuilder; +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.hl7.fhir.dstu3.model.HumanName; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; + +public class VersionedApiConverterInterceptorR4Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(VersionedApiConverterInterceptorR4Test.class); + private static CloseableHttpClient ourClient; + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static int ourPort; + + private static Server ourServer; + + + @Test + public void testSearchNormal() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + try (CloseableHttpResponse status = ourClient.execute(httpGet)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertThat(responseContent, containsString("\"family\": \"FAMILY\"")); + } + } + + @Test + public void testSearchConvertToR2() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + httpGet.addHeader("Accept", "application/fhir+json; fhirVersion=1.0"); + try (CloseableHttpResponse status = ourClient.execute(httpGet)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertThat(responseContent, containsString("\"family\": [")); + } + } + + @Test + public void testSearchConvertToR2ByFormatParam() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=" + UrlUtil.escapeUrlParam("application/fhir+json; fhirVersion=1.0")); + try (CloseableHttpResponse status = ourClient.execute(httpGet)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertThat(responseContent, containsString("\"family\": [")); + } + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = PortUtil.findFreePort(); + ourServer = new Server(ourPort); + + DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(ourCtx); + servlet.setDefaultResponseEncoding(EncodingEnum.JSON); + servlet.setDefaultPrettyPrint(true); + servlet.registerInterceptor(new VersionedApiConverterInterceptor()); + + servlet.setResourceProviders(patientProvider); + ServletHolder servletHolder = new ServletHolder(servlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + + } + + public static class DummyPatientResourceProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @SuppressWarnings("rawtypes") + @Search() + public List search() { + ArrayList retVal = new ArrayList<>(); + + Patient patient = new Patient(); + patient.getIdElement().setValue("Patient/A"); + patient.addName(new HumanName().setFamily("FAMILY")); + retVal.add(patient); + + return retVal; + } + + } + +} diff --git a/hapi-fhir-converter/src/test/resources/logback-test.xml b/hapi-fhir-converter/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..91f8a74d3e2 --- /dev/null +++ b/hapi-fhir-converter/src/test/resources/logback-test.xml @@ -0,0 +1,48 @@ + + + + + INFO + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index 9071b801603..182421e83c0 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -91,7 +91,7 @@ public class JaxRsResponse extends RestfulResponse { StringWriter writer = new StringWriter(); if (outcome != null) { FhirContext fhirContext = getRequestDetails().getServer().getFhirContext(); - IParser parser = RestfulServerUtils.getNewParser(fhirContext, getRequestDetails()); + IParser parser = RestfulServerUtils.getNewParser(fhirContext, fhirContext.getVersion().getVersion(), getRequestDetails()); outcome.execute(parser, writer); } return sendWriterResponse(operationStatus, getParserType(), null, writer); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseDstu3Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseDstu3Test.java index cfcf62001d9..94126fa48d7 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseDstu3Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseDstu3Test.java @@ -114,7 +114,7 @@ public class JaxRsResponseDstu3Test { boolean allowPrefer = true; String resourceName = "Patient"; MethodOutcome methodOutcome = new MethodOutcome(theId); - response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); + response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); boolean addContentLocationHeader = true; boolean respondGzip = true; Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request); @@ -126,7 +126,7 @@ public class JaxRsResponseDstu3Test { @Test public void testNoOutcomeXml() throws IOException { - response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); + response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); boolean addContentLocationHeader = true; boolean respondGzip = true; Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), null, theSummaryMode, 204, addContentLocationHeader, respondGzip, this.request); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java index 1a4bffb1ad9..b9979da676b 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java @@ -88,7 +88,7 @@ public class JaxRsResponseTest { @Test public void testReturnResponseAsXml() throws IOException { - response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML }); + response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML }); boolean addContentLocationHeader = true; boolean respondGzip = true; Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request); @@ -100,7 +100,7 @@ public class JaxRsResponseTest { @Test public void testNoOutcomeXml() throws IOException { - response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML }); + response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML }); boolean addContentLocationHeader = true; boolean respondGzip = true; Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request); diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index b103623a495..bbe19c60c6f 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -550,6 +550,27 @@ + + + + javax.xml.bind + jaxb-api + ${jaxb_api_version} + + + com.sun.xml.bind + jaxb-core + ${jaxb_core_version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb_core_version} + + org.jacoco 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 987bb3e947b..068d59250e8 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 @@ -18,7 +18,7 @@ import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.validation.IValidatorModule; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel; +import org.hl7.fhir.r4.utils.IResourceValidator; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -66,7 +66,7 @@ public class BaseDstu3Config extends BaseConfig { @Lazy public IValidatorModule instanceValidatorDstu3() { FhirInstanceValidator val = new FhirInstanceValidator(); - val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning); + val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); val.setValidationSupport(validationSupportChainDstu3()); return val; } 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 6b9225bad9e..b044f6d57c2 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 @@ -532,7 +532,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } for (java.util.Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]); - requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue); + requestDetails.addParameter(nextParamEntry.getKey(), nextValue); } url = url.substring(0, qIndex); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 13c06ba885d..70ed8ec955b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -260,7 +260,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } for (java.util.Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]); - requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue); + requestDetails.addParameter(nextParamEntry.getKey(), nextValue); } url = url.substring(0, qIndex); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java index 7ddf0286dda..6b2ad8519f1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java @@ -266,7 +266,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { } for (java.util.Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]); - requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue); + requestDetails.addParameter(nextParamEntry.getKey(), nextValue); } url = url.substring(0, qIndex); } @@ -290,7 +290,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch()); } - Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url); + Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {0}", url); try { IBaseResource resource = ((BaseResourceReturningMethodBinding) method).doInvokeServer(theRequestDetails.getServer(), requestDetails); if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java index 254b30683ef..666200cb0a5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java @@ -29,13 +29,13 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; public class ServletSubRequestDetails extends ServletRequestDetails { - private Map> myHeaders = new HashMap>(); + private Map> myHeaders = new HashMap<>(); public void addHeader(String theName, String theValue) { String lowerCase = theName.toLowerCase(); ArrayList list = myHeaders.get(lowerCase); if (list == null) { - list = new ArrayList(); + list = new ArrayList<>(); myHeaders.put(lowerCase, list); } list.add(theValue); 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 bca7e69e457..c8a7c35f75c 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 @@ -158,7 +158,7 @@ public abstract class BaseJpaTest { } protected List toUnqualifiedVersionlessIds(IBundleProvider theFound) { - List retVal = new ArrayList(); + List retVal = new ArrayList<>(); Integer size = theFound.size(); StopWatch sw = new StopWatch(); while (size == null) { @@ -171,6 +171,7 @@ public abstract class BaseJpaTest { } catch (InterruptedException theE) { //ignore } + size = theFound.size(); } ourLog.info("Found {} results", size); 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 2cf97e67ef4..b845cef3134 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 @@ -46,6 +46,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { @Before public void before() { myDaoConfig.setDefaultSearchParamsCanBeOverridden(true); + myDaoConfig.setSchedulingDisabled(new DaoConfig().isSchedulingDisabled()); } private void createUniqueBirthdateAndGenderSps() { @@ -411,6 +412,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { @Test public void testDuplicateUniqueValuesAreReIndexed() { + myDaoConfig.setSchedulingDisabled(true); Patient pt1 = new Patient(); pt1.setActive(true); @@ -449,7 +451,9 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { myResourceIndexedCompositeStringUniqueDao.deleteAll(); - mySystemDao.markAllResourcesForReindexing(); + assertEquals(1, mySearchParamRegsitry.getActiveUniqueSearchParams("Observation").size()); + + assertEquals(7, mySystemDao.markAllResourcesForReindexing()); mySystemDao.performReindexingPass(1000); mySystemDao.performReindexingPass(1000); 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 68a67d5b89a..31cfcb39d78 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 @@ -3,7 +3,6 @@ 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.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; @@ -12,6 +11,7 @@ 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.TokenOrListParam; import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.TestUtil; @@ -24,7 +24,6 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.junit.*; import org.mockito.ArgumentCaptor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -2448,7 +2447,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { /* * Make sure we are able to handle placeholder IDs in match URLs, e.g. - * + * * "request": { * "method": "PUT", * "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00" @@ -2478,7 +2477,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { /* * Make sure we are able to handle placeholder IDs in match URLs, e.g. - * + * * "request": { * "method": "PUT", * "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00" @@ -2542,7 +2541,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString()); } - @Test public void testTransactionWithReferenceResource() { Bundle request = new Bundle(); @@ -2570,7 +2568,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { assertEquals(1, found.size().intValue()); } - @Test public void testTransactionWithReferenceToCreateIfNoneExist() { Bundle bundle = new Bundle(); @@ -2594,9 +2591,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); - /* - * Again! - */ + /* + * Again! + */ bundle = new Bundle(); bundle.setType(BundleType.TRANSACTION); @@ -2624,6 +2621,33 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { assertNotEquals(medOrderId1, medOrderId2); } + @Test + public void testTransactionWithReferenceUuid() { + Bundle request = new Bundle(); + + Patient p = new Patient(); + p.setActive(true); + p.setId(IdType.newRandomUuid()); + request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId()); + + Observation o = new Observation(); + o.getCode().setText("Some Observation"); + o.getSubject().setReference(p.getId()); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); + + Bundle resp = mySystemDao.transaction(mySrd, request); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + + String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue(); + assertThat(patientId, startsWith("Patient/")); + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add("subject", new ReferenceParam(patientId)); + IBundleProvider found = myObservationDao.search(params); + assertEquals(1, found.size().intValue()); + } + // // // /** @@ -2726,34 +2750,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { // // } - - @Test - public void testTransactionWithReferenceUuid() { - Bundle request = new Bundle(); - - Patient p = new Patient(); - p.setActive(true); - p.setId(IdType.newRandomUuid()); - request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId()); - - Observation o = new Observation(); - o.getCode().setText("Some Observation"); - o.getSubject().setReference(p.getId()); - request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST); - - Bundle resp = mySystemDao.transaction(mySrd, request); - ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); - - String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue(); - assertThat(patientId, startsWith("Patient/")); - - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - params.add("subject", new ReferenceParam(patientId)); - IBundleProvider found = myObservationDao.search(params); - assertEquals(1, found.size().intValue()); - } - @Test public void testTransactionWithRelativeOidIds() throws Exception { Bundle res = new Bundle(); @@ -2918,6 +2914,46 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { } + /** + * See #811 + */ + @Test + public void testUpdatePreviouslyDeletedResourceInBatch() { + AllergyIntolerance ai = new AllergyIntolerance(); + ai.setId("AIA1914009"); + ai.setClinicalStatus(AllergyIntolerance.AllergyIntoleranceClinicalStatus.ACTIVE); + IIdType id = myAllergyIntoleranceDao.update(ai).getId(); + assertEquals("1", id.getVersionIdPart()); + + id = myAllergyIntoleranceDao.delete(ai.getIdElement().toUnqualifiedVersionless()).getId(); + assertEquals("2", id.getVersionIdPart()); + + try { + myAllergyIntoleranceDao.read(ai.getIdElement().toUnqualifiedVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good + } + + Bundle batch = new Bundle(); + batch.setType(BundleType.BATCH); + ai = new AllergyIntolerance(); + ai.setId("AIA1914009"); + ai.setClinicalStatus(AllergyIntolerance.AllergyIntoleranceClinicalStatus.ACTIVE); + batch + .addEntry() + .setFullUrl("AllergyIntolerance/AIA1914009") + .setResource(ai) + .getRequest() + .setUrl("AllergyIntolerance/AIA1914009") + .setMethod(HTTPVerb.PUT); + mySystemDao.transaction(mySrd, batch); + + id = myAllergyIntoleranceDao.read(ai.getIdElement().toUnqualifiedVersionless()).getIdElement(); + assertEquals("3", id.getVersionIdPart()); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); 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 0576915716b..7219a0025a4 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,35 +1,41 @@ package ca.uhn.fhir.jpa.provider.r4; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.rp.r4.*; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.util.TestUtil; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; -import org.hl7.fhir.r4.model.Patient; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.rp.r4.*; -import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { @@ -43,7 +49,6 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; - @SuppressWarnings("deprecation") @After public void after() { @@ -57,7 +62,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); ourClient.registerInterceptor(mySimpleHeaderInterceptor); } - + @Before public void beforeStartServer() throws Exception { if (myRestServer == null) { @@ -73,8 +78,14 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); organizationRp.setDao(myOrganizationDao); + MedicationResourceProvider medicationRp = new MedicationResourceProvider(); + medicationRp.setDao(myMedicationDao); + + MedicationRequestResourceProvider medicationRequestRp = new MedicationRequestResourceProvider(); + medicationRequestRp.setDao(myMedicationRequestDao); + RestfulServer restServer = new RestfulServer(ourCtx); - restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp, medicationRequestRp, medicationRp); restServer.setPlainProviders(mySystemProvider); @@ -106,11 +117,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { ourClient.setLogRequestAndResponse(true); myRestServer = restServer; } - + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); myRestServer.setPagingProvider(myPagingProvider); } - private List create20Patients() { List ids = new ArrayList(); @@ -120,7 +130,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { patient.setId("" + letter); patient.setGender(AdministrativeGender.MALE); patient.addIdentifier().setSystem("urn:foo").setValue("A"); - patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1)); + patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i + 1)); String id = myPatientDao.update(patient).getId().toUnqualifiedVersionless().getValue(); ids.add(id); } @@ -130,7 +140,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { @Test public void testBatchWithGetHardLimitLargeSynchronous() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.BATCH); input @@ -138,12 +148,12 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .getRequest() .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&_sort=_id"); - + myDaoConfig.setMaximumSearchResultCountInTransaction(100); - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(1, output.getEntry().size()); Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); assertEquals(5, respBundle.getEntry().size()); @@ -151,11 +161,11 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { List actualIds = toIds(respBundle); assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); } - + @Test public void testBatchWithGetNormalSearch() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.BATCH); input @@ -163,16 +173,16 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .getRequest() .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&_sort=name"); - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(1, output.getEntry().size()); Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); assertEquals(5, respBundle.getEntry().size()); List actualIds = toIds(respBundle); assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); - + String nextPageLink = respBundle.getLink("next").getUrl(); output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); respBundle = output; @@ -188,7 +198,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { public void testBatchWithManyGets() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.BATCH); for (int i = 0; i < 30; i++) { @@ -198,10 +208,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); } - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(30, output.getEntry().size()); for (int i = 0; i < 30; i++) { Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); @@ -212,10 +222,72 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { } } + /** + * See #822 + */ + @Test + public void testSearchByBatch() { + Patient p = new Patient(); + p.setId("P3000254749"); + p.setActive(true); + myPatientDao.update(p); + + Medication med = new Medication(); + med.setId("MED19795"); + med.getCode().addCoding().setCode("00093-0058-05").setSystem("http://hl7.org/fhir/sid/ndc"); + myMedicationDao.update(med); + + med = new Medication(); + med.setId("MED20344"); + med.getCode().addCoding().setCode("50580-0449-23").setSystem("http://hl7.org/fhir/sid/ndc"); + myMedicationDao.update(med); + + MedicationRequest medRequest = new MedicationRequest(); + medRequest.setId("MR142528"); + medRequest.setMedication(new Reference("Medication/MED19795")); + medRequest.setSubject(new Reference("Patient/P3000254749")); + medRequest.setIntent(MedicationRequest.MedicationRequestIntent.ORDER); + myMedicationRequestDao.update(medRequest); + + medRequest = new MedicationRequest(); + medRequest.setId("MR635079"); + medRequest.setMedication(new Reference("Medication/MED20344")); + medRequest.setSubject(new Reference("Patient/P3000254749")); + medRequest.setIntent(MedicationRequest.MedicationRequestIntent.ORDER); + myMedicationRequestDao.update(medRequest); + + SearchParameterMap map = new SearchParameterMap(); + map.add(MedicationRequest.SP_INTENT, new TokenOrListParam().add(null, "plan").add(null, "order")); + map.add(MedicationRequest.SP_MEDICATION, new ReferenceParam().setChain("code").setValue("50580-0449-23")); + Bundle b = ourClient + .search() + .forResource("MedicationRequest") + .where(MedicationRequest.INTENT.exactly().codes("plan", "order")) + .and(MedicationRequest.MEDICATION.hasChainedProperty(Medication.CODE.exactly().code("50580-0449-23"))) + .returnBundle(Bundle.class) + .execute(); + assertEquals(1, b.getEntry().size()); + assertEquals("MedicationRequest/MR635079", b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + + b = new Bundle(); + b.setType(BundleType.BATCH); + b.addEntry() + .setFullUrl(IdType.newRandomUuid().getValueAsString()) + .getRequest() + .setMethod(HTTPVerb.GET) + .setUrl("MedicationRequest?intent=plan,order&medication.code=50580-0449-23&patient=P3000254749"); + Bundle resp = ourClient.transaction().withBundle(b).execute(); + + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp)); + b = (Bundle) resp.getEntry().get(0).getResource(); + assertEquals(1, b.getEntry().size()); + assertEquals("MedicationRequest/MR635079", b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + @Test public void testTransactionWithGetHardLimitLargeSynchronous() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.TRANSACTION); input @@ -223,12 +295,12 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .getRequest() .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&_sort=_id"); - + myDaoConfig.setMaximumSearchResultCountInTransaction(100); - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(1, output.getEntry().size()); Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); assertEquals(5, respBundle.getEntry().size()); @@ -236,11 +308,11 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { List actualIds = toIds(respBundle); assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); } - + @Test public void testTransactionWithGetNormalSearch() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.TRANSACTION); input @@ -248,16 +320,16 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .getRequest() .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&_sort=name"); - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(1, output.getEntry().size()); Bundle respBundle = (Bundle) output.getEntry().get(0).getResource(); assertEquals(5, respBundle.getEntry().size()); List actualIds = toIds(respBundle); assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0]))); - + String nextPageLink = respBundle.getLink("next").getUrl(); output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute(); respBundle = output; @@ -273,7 +345,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { public void testTransactionWithManyGets() { List ids = create20Patients(); - + Bundle input = new Bundle(); input.setType(BundleType.TRANSACTION); for (int i = 0; i < 30; i++) { @@ -283,10 +355,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { .setMethod(HTTPVerb.GET) .setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i); } - + Bundle output = ourClient.transaction().withBundle(input).execute(); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output)); - + assertEquals(30, output.getEntry().size()); for (int i = 0; i < 30; i++) { Bundle respBundle = (Bundle) output.getEntry().get(i).getResource(); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 025558219df..2dfc1ec5008 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -155,6 +155,11 @@ org.apache.commons commons-dbcp2
+ + ca.uhn.hapi.fhir + hapi-fhir-converter + 3.3.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 a77e7c7b5de..e564b337c23 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 @@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhirtest.config.*; +import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; @@ -176,6 +177,11 @@ public class TestRestfulServer extends RestfulServer { CorsInterceptor corsInterceptor = new CorsInterceptor(); registerInterceptor(corsInterceptor); + /* + * Enable version conversion + */ + registerInterceptor(new VersionedApiConverterInterceptor()); + /* * We want to format the response using nice HTML if it's a browser, since this * makes things a little easier for testers. 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 b1b957a2b09..124258a04ea 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 @@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -30,9 +31,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank; * 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. @@ -63,6 +64,11 @@ public abstract class RequestDetails { private Map> myUnqualifiedToQualifiedNames; private Map myUserData; + public void addParameter(String theName, String[] theValues) { + getParameters(); + myParameters.put(theName, theValues); + } + protected abstract byte[] getByteStreamRequestContents(); /** @@ -170,37 +176,14 @@ public abstract class RequestDetails { public Map getParameters() { if (myParameters == null) { - return Collections.emptyMap(); + myParameters = new HashMap<>(); } - return myParameters; + return Collections.unmodifiableMap(myParameters); } public void setParameters(Map theParams) { myParameters = theParams; - - for (String next : theParams.keySet()) { - for (int i = 0; i < next.length(); i++) { - char nextChar = next.charAt(i); - if (nextChar == ':' || nextChar == '.') { - if (myUnqualifiedToQualifiedNames == null) { - myUnqualifiedToQualifiedNames = new HashMap<>(); - } - String unqualified = next.substring(0, i); - List list = myUnqualifiedToQualifiedNames.get(unqualified); - if (list == null) { - list = new ArrayList<>(4); - myUnqualifiedToQualifiedNames.put(unqualified, list); - } - list.add(next); - break; - } - } - } - - if (myUnqualifiedToQualifiedNames == null) { - myUnqualifiedToQualifiedNames = Collections.emptyMap(); - } - + myUnqualifiedToQualifiedNames = null; } /** @@ -296,6 +279,31 @@ public abstract class RequestDetails { } public Map> getUnqualifiedToQualifiedNames() { + if (myUnqualifiedToQualifiedNames == null) { + for (String next : myParameters.keySet()) { + for (int i = 0; i < next.length(); i++) { + char nextChar = next.charAt(i); + if (nextChar == ':' || nextChar == '.') { + if (myUnqualifiedToQualifiedNames == null) { + myUnqualifiedToQualifiedNames = new HashMap<>(); + } + String unqualified = next.substring(0, i); + List list = myUnqualifiedToQualifiedNames.get(unqualified); + if (list == null) { + list = new ArrayList<>(4); + myUnqualifiedToQualifiedNames.put(unqualified, list); + } + list.add(next); + break; + } + } + } + } + + if (myUnqualifiedToQualifiedNames == null) { + myUnqualifiedToQualifiedNames = Collections.emptyMap(); + } + return myUnqualifiedToQualifiedNames; } @@ -359,6 +367,12 @@ public abstract class RequestDetails { return myRequestContents; } + public void removeParameter(String theName) { + Validate.notNull(theName, "theName must not be null"); + getParameters(); + myParameters.remove(theName); + } + /** * This method may be used to modify the contents of the incoming * request by hardcoding a value which will be used instead of the 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 cfa5c0da1b6..52d998878dd 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 @@ -938,8 +938,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer TEXT_ENCODE_ELEMENTS = new HashSet(Arrays.asList("Bundle", "*.text", "*.(mandatory)")); + private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap()); public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) { // Pretty print @@ -266,7 +267,7 @@ public class RestfulServerUtils { * Some browsers (e.g. FF) request "application/xml" in their Accept header, * and we generally want to treat this as a preference for FHIR XML even if * it's not the FHIR version of the CT, which should be "application/xml+fhir". - * + * * When we're serving up Binary resources though, we are a bit more strict, * since Binary is supposed to use native content types unless the client has * explicitly requested FHIR. @@ -433,7 +434,9 @@ public class RestfulServerUtils { if (theResourceId.hasIdPart() && isNotBlank(theServerBase)) { String resName = theResourceId.getResourceType(); if (theResource != null && isBlank(resName)) { - resName = theServer.getFhirContext().getResourceDefinition(theResource).getName(); + FhirContext context = theServer.getFhirContext(); + context = getContextForVersion(context, theResource.getStructureFhirVersionEnum()); + resName = context.getResourceDefinition(theResource).getName(); } if (isNotBlank(resName)) { retVal = theResourceId.withServerBase(theServerBase, resName); @@ -455,18 +458,19 @@ public class RestfulServerUtils { return new ResponseEncoding(theFhirContext, encoding, theContentType); } - public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { + public static IParser getNewParser(FhirContext theContext, FhirVersionEnum theForVersion, RequestDetails theRequestDetails) { + FhirContext context = getContextForVersion(theContext, theForVersion); // Determine response encoding EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding(); IParser parser; switch (responseEncoding) { case JSON: - parser = theContext.newJsonParser(); + parser = context.newJsonParser(); break; case XML: default: - parser = theContext.newXmlParser(); + parser = context.newXmlParser(); break; } @@ -475,6 +479,18 @@ public class RestfulServerUtils { return parser; } + private static FhirContext getContextForVersion(FhirContext theContext, FhirVersionEnum theForVersion) { + FhirContext context = theContext; + if (context.getVersion().getVersion() != theForVersion) { + context = myFhirContextMap.get(theForVersion); + if (context == null) { + context = theForVersion.newContext(); + myFhirContextMap.put(theForVersion, context); + } + } + return context; + } + public static Set parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) { Set retVal = new HashSet(); @@ -689,7 +705,8 @@ public class RestfulServerUtils { } else if (encodingDomainResourceAsText && theResource instanceof IResource) { writer.append(((IResource) theResource).getText().getDiv().getValueAsString()); } else { - IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails); + FhirVersionEnum forVersion = theResource.getStructureFhirVersionEnum(); + IParser parser = getNewParser(theServer.getFhirContext(), forVersion, theRequestDetails); parser.encodeResourceToWriter(theResource, writer); } //FIXME resource leak diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java index 2f2faa422cd..30e49acce57 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.server.interceptor; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -322,10 +323,10 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { force = true; } else if (Constants.FORMATS_HTML_XML.equals(formatParam)) { force = true; - theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_XML); + theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_XML); } else if (Constants.FORMATS_HTML_JSON.equals(formatParam)) { force = true; - theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON); + theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON); } else { return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); } @@ -392,7 +393,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { } } - private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest, int theStatusCode) { + private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource theResource, ServletRequest theServletRequest, int theStatusCode) { if (theRequestDetails.getServer() instanceof RestfulServer) { RestfulServer rs = (RestfulServer) theRequestDetails.getServer(); @@ -402,7 +403,8 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { IParser p; Map parameters = theRequestDetails.getParameters(); if (parameters.containsKey(Constants.PARAM_FORMAT)) { - p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails); + FhirVersionEnum forVersion = theResource.getStructureFhirVersionEnum(); + p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), forVersion, theRequestDetails); } else { EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding(); p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext()); @@ -423,7 +425,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { } EncodingEnum encoding = p.getEncoding(); - String encoded = p.encodeResourceToString(resource); + String encoded = p.encodeResourceToString(theResource); try { @@ -615,7 +617,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { b.append("\n"); InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js"); - String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript resource not found')"; + String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')"; jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest()); b.append("