An experimental interceptor called VersionedApiConverterInterceptor has been added, which automaticaly converts response payloads to a client-specified version according to transforms built into FHIR.
This commit is contained in:
parent
a89c8d50c5
commit
e52f582769
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.context;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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")),
|
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()),
|
DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()),
|
||||||
|
|
||||||
R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),
|
R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),;
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
private final FhirVersionEnum myEquivalent;
|
private final FhirVersionEnum myEquivalent;
|
||||||
private final boolean myIsRi;
|
private final boolean myIsRi;
|
||||||
private volatile Boolean myPresentOnClasspath;
|
|
||||||
private final String myVersionClass;
|
private final String myVersionClass;
|
||||||
|
private volatile Boolean myPresentOnClasspath;
|
||||||
private volatile IFhirVersion myVersionImplementation;
|
private volatile IFhirVersion myVersionImplementation;
|
||||||
private String myFhirVersionString;
|
private String myFhirVersionString;
|
||||||
|
|
||||||
|
@ -82,21 +80,6 @@ public enum FhirVersionEnum {
|
||||||
return ordinal() >= theVersion.ordinal();
|
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) {
|
public boolean isEquivalentTo(FhirVersionEnum theVersion) {
|
||||||
if (this.equals(theVersion)) {
|
if (this.equals(theVersion)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -139,15 +122,50 @@ public enum FhirVersionEnum {
|
||||||
return myIsRi;
|
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 static class Version implements IVersionProvider {
|
||||||
|
|
||||||
|
private String myVersion;
|
||||||
|
|
||||||
public Version(String theVersion) {
|
public Version(String theVersion) {
|
||||||
super();
|
super();
|
||||||
myVersion = theVersion;
|
myVersion = theVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String myVersion;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String provideVersion() {
|
public String provideVersion() {
|
||||||
return myVersion;
|
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
|
* This class attempts to read the FHIR version from the actual model
|
||||||
* classes in order to supply an accurate version string even over time
|
* classes in order to supply an accurate version string even over time
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private static class Dstu3Version implements IVersionProvider {
|
private static class Dstu3Version implements IVersionProvider {
|
||||||
|
|
||||||
|
private String myVersion;
|
||||||
|
|
||||||
public Dstu3Version() {
|
public Dstu3Version() {
|
||||||
try {
|
try {
|
||||||
Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
|
Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
|
||||||
|
@ -175,8 +190,6 @@ public enum FhirVersionEnum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String myVersion;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String provideVersion() {
|
public String provideVersion() {
|
||||||
return myVersion;
|
return myVersion;
|
||||||
|
@ -186,6 +199,8 @@ public enum FhirVersionEnum {
|
||||||
|
|
||||||
private static class R4Version implements IVersionProvider {
|
private static class R4Version implements IVersionProvider {
|
||||||
|
|
||||||
|
private String myVersion;
|
||||||
|
|
||||||
public R4Version() {
|
public R4Version() {
|
||||||
try {
|
try {
|
||||||
Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
|
Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
|
||||||
|
@ -195,8 +210,6 @@ public enum FhirVersionEnum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String myVersion;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String provideVersion() {
|
public String provideVersion() {
|
||||||
return myVersion;
|
return myVersion;
|
||||||
|
|
|
@ -32,6 +32,12 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-structures-dstu2</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||||
|
@ -70,6 +76,11 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Testing -->
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-client</artifactId>
|
<artifactId>hapi-fhir-client</artifactId>
|
||||||
|
|
|
@ -1,39 +1,68 @@
|
||||||
package ca.uhn.hapi.converters.server;
|
package ca.uhn.hapi.converters.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
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.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
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.exceptions.FHIRException;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>This is an experimental interceptor! Use with caution as
|
||||||
|
* behaviour may change or be removed in a future version of
|
||||||
|
* FHIR.</b>
|
||||||
|
* <p>
|
||||||
|
* This interceptor partially implements the proposed
|
||||||
|
* Versioned API features.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public class VersionedApiConverterInterceptor extends InterceptorAdapter {
|
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
|
@Override
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
|
public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
|
||||||
String accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT));
|
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, ";");
|
StringTokenizer tok = new StringTokenizer(accept, ";");
|
||||||
String wantVersionString = null;
|
String wantVersionString = null;
|
||||||
while (tok.hasMoreTokens()) {
|
while (tok.hasMoreTokens()) {
|
||||||
String next = tok.nextToken().trim();
|
String next = tok.nextToken().trim();
|
||||||
if (next.startsWith("fhir-version=")) {
|
if (next.startsWith("fhirVersion=")) {
|
||||||
wantVersionString = next.substring("fhir-version=".length()).trim();
|
wantVersionString = next.substring("fhirVersion=".length()).trim();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,31 +71,48 @@ public class VersionedApiConverterInterceptor extends InterceptorAdapter {
|
||||||
if (isNotBlank(wantVersionString)) {
|
if (isNotBlank(wantVersionString)) {
|
||||||
wantVersion = FhirVersionEnum.forVersionString(wantVersionString);
|
wantVersion = FhirVersionEnum.forVersionString(wantVersionString);
|
||||||
}
|
}
|
||||||
FhirVersionEnum haveVersion = theResponseObject.getStructureFhirVersionEnum();
|
|
||||||
|
IBaseResource responseResource = theResponseDetails.getResponseResource();
|
||||||
|
FhirVersionEnum haveVersion = responseResource.getStructureFhirVersionEnum();
|
||||||
|
|
||||||
IBaseResource converted = null;
|
IBaseResource converted = null;
|
||||||
try {
|
try {
|
||||||
if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU3) {
|
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) {
|
} 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.DSTU3 && haveVersion == FhirVersionEnum.R4) {
|
} else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.R4) {
|
||||||
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject);
|
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) {
|
} catch (FHIRException e) {
|
||||||
throw new InternalErrorException(e);
|
throw new InternalErrorException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (converted != null) {
|
if (converted != null) {
|
||||||
Set<SummaryEnum> objects = Collections.emptySet();
|
theResponseDetails.setResponseResource(converted);
|
||||||
try {
|
|
||||||
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), converted, objects, 200, "OK", false, false, theRequestDetails, null, null);
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalErrorException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<? extends IBaseResource> getResourceType() {
|
|
||||||
return Patient.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Search()
|
|
||||||
public List search(
|
|
||||||
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
|
|
||||||
ourLastMethod = "search";
|
|
||||||
ourIdentifiers = theIdentifiers;
|
|
||||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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<? extends IBaseResource> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Search()
|
||||||
|
public List search() {
|
||||||
|
ArrayList<Patient> retVal = new ArrayList<>();
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.getIdElement().setValue("Patient/A");
|
||||||
|
patient.addName(new HumanName().setFamily("FAMILY"));
|
||||||
|
retVal.add(patient);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.eclipse.jetty.websocket" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.eclipse" additivity="false" level="error">
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- Set to 'trace' to enable SQL logging -->
|
||||||
|
<logger name="org.hibernate.SQL" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
<!-- Set to 'trace' to enable SQL Value logging -->
|
||||||
|
<logger name="org.hibernate.type" additivity="false" level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -91,7 +91,7 @@ public class JaxRsResponse extends RestfulResponse<JaxRsRequest> {
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
if (outcome != null) {
|
if (outcome != null) {
|
||||||
FhirContext fhirContext = getRequestDetails().getServer().getFhirContext();
|
FhirContext fhirContext = getRequestDetails().getServer().getFhirContext();
|
||||||
IParser parser = RestfulServerUtils.getNewParser(fhirContext, getRequestDetails());
|
IParser parser = RestfulServerUtils.getNewParser(fhirContext, fhirContext.getVersion().getVersion(), getRequestDetails());
|
||||||
outcome.execute(parser, writer);
|
outcome.execute(parser, writer);
|
||||||
}
|
}
|
||||||
return sendWriterResponse(operationStatus, getParserType(), null, writer);
|
return sendWriterResponse(operationStatus, getParserType(), null, writer);
|
||||||
|
|
|
@ -155,6 +155,11 @@
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-dbcp2</artifactId>
|
<artifactId>commons-dbcp2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-converter</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -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.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||||
import ca.uhn.fhirtest.config.*;
|
import ca.uhn.fhirtest.config.*;
|
||||||
|
import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.context.ContextLoaderListener;
|
import org.springframework.web.context.ContextLoaderListener;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
@ -176,6 +177,11 @@ public class TestRestfulServer extends RestfulServer {
|
||||||
CorsInterceptor corsInterceptor = new CorsInterceptor();
|
CorsInterceptor corsInterceptor = new CorsInterceptor();
|
||||||
registerInterceptor(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
|
* We want to format the response using nice HTML if it's a browser, since this
|
||||||
* makes things a little easier for testers.
|
* makes things a little easier for testers.
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -58,6 +58,7 @@ public class RestfulServerUtils {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
|
||||||
|
|
||||||
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text", "*.(mandatory)"));
|
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text", "*.(mandatory)"));
|
||||||
|
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<FhirVersionEnum, FhirContext>());
|
||||||
|
|
||||||
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
|
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
|
||||||
// Pretty print
|
// Pretty print
|
||||||
|
@ -266,7 +267,7 @@ public class RestfulServerUtils {
|
||||||
* Some browsers (e.g. FF) request "application/xml" in their Accept header,
|
* 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
|
* 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".
|
* 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,
|
* 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
|
* since Binary is supposed to use native content types unless the client has
|
||||||
* explicitly requested FHIR.
|
* explicitly requested FHIR.
|
||||||
|
@ -433,7 +434,9 @@ public class RestfulServerUtils {
|
||||||
if (theResourceId.hasIdPart() && isNotBlank(theServerBase)) {
|
if (theResourceId.hasIdPart() && isNotBlank(theServerBase)) {
|
||||||
String resName = theResourceId.getResourceType();
|
String resName = theResourceId.getResourceType();
|
||||||
if (theResource != null && isBlank(resName)) {
|
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)) {
|
if (isNotBlank(resName)) {
|
||||||
retVal = theResourceId.withServerBase(theServerBase, resName);
|
retVal = theResourceId.withServerBase(theServerBase, resName);
|
||||||
|
@ -455,18 +458,19 @@ public class RestfulServerUtils {
|
||||||
return new ResponseEncoding(theFhirContext, encoding, theContentType);
|
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
|
// Determine response encoding
|
||||||
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding();
|
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding();
|
||||||
IParser parser;
|
IParser parser;
|
||||||
switch (responseEncoding) {
|
switch (responseEncoding) {
|
||||||
case JSON:
|
case JSON:
|
||||||
parser = theContext.newJsonParser();
|
parser = context.newJsonParser();
|
||||||
break;
|
break;
|
||||||
case XML:
|
case XML:
|
||||||
default:
|
default:
|
||||||
parser = theContext.newXmlParser();
|
parser = context.newXmlParser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,6 +479,18 @@ public class RestfulServerUtils {
|
||||||
return parser;
|
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<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
|
public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
|
||||||
Set<String> retVal = new HashSet<String>();
|
Set<String> retVal = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -689,7 +705,8 @@ public class RestfulServerUtils {
|
||||||
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
|
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
|
||||||
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
|
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
|
||||||
} else {
|
} else {
|
||||||
IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails);
|
FhirVersionEnum forVersion = theResource.getStructureFhirVersionEnum();
|
||||||
|
IParser parser = getNewParser(theServer.getFhirContext(), forVersion, theRequestDetails);
|
||||||
parser.encodeResourceToWriter(theResource, writer);
|
parser.encodeResourceToWriter(theResource, writer);
|
||||||
}
|
}
|
||||||
//FIXME resource leak
|
//FIXME resource leak
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.rest.server.interceptor;
|
package ca.uhn.fhir.rest.server.interceptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
|
@ -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) {
|
if (theRequestDetails.getServer() instanceof RestfulServer) {
|
||||||
RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
|
RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
|
||||||
|
@ -402,7 +403,8 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
|
||||||
IParser p;
|
IParser p;
|
||||||
Map<String, String[]> parameters = theRequestDetails.getParameters();
|
Map<String, String[]> parameters = theRequestDetails.getParameters();
|
||||||
if (parameters.containsKey(Constants.PARAM_FORMAT)) {
|
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 {
|
} else {
|
||||||
EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
|
EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
|
||||||
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
|
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
|
||||||
|
@ -423,7 +425,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
EncodingEnum encoding = p.getEncoding();
|
EncodingEnum encoding = p.getEncoding();
|
||||||
String encoded = p.encodeResourceToString(resource);
|
String encoded = p.encodeResourceToString(theResource);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -615,7 +617,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
|
||||||
b.append("\n");
|
b.append("\n");
|
||||||
|
|
||||||
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
|
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());
|
jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest());
|
||||||
b.append("<script type=\"text/javascript\">");
|
b.append("<script type=\"text/javascript\">");
|
||||||
b.append(jsStr);
|
b.append(jsStr);
|
||||||
|
|
|
@ -74,6 +74,11 @@
|
||||||
it is possible to perform Schematron validation on Java 9. Thanks to
|
it is possible to perform Schematron validation on Java 9. Thanks to
|
||||||
Mark Grimes for reporting and suggesting a fix!
|
Mark Grimes for reporting and suggesting a fix!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
An experimental interceptor called VersionedApiConverterInterceptor has been added,
|
||||||
|
which automaticaly converts response payloads to a client-specified version
|
||||||
|
according to transforms built into FHIR.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.2.0" date="2018-01-13">
|
<release version="3.2.0" date="2018-01-13">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue