Forward port #705, #708, and #710

Merge branch 'master' into hapi3_refactor
This commit is contained in:
James 2017-08-13 12:31:09 -04:00
commit 3b80779fd3
12 changed files with 91 additions and 66 deletions

View File

@ -140,6 +140,11 @@ public enum FhirVersionEnum {
String provideVersion(); 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 static class Dstu3Version implements IVersionProvider {
public Dstu3Version() { public Dstu3Version() {
@ -147,7 +152,7 @@ public enum FhirVersionEnum {
Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants"); Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
myVersion = (String) c.getDeclaredField("VERSION").get(null); myVersion = (String) c.getDeclaredField("VERSION").get(null);
} catch (Exception e) { } catch (Exception e) {
myVersion = "UNKNOWN"; myVersion = "3.0.1";
} }
} }
@ -167,7 +172,7 @@ public enum FhirVersionEnum {
Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants"); Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
myVersion = (String) c.getDeclaredField("VERSION").get(null); myVersion = (String) c.getDeclaredField("VERSION").get(null);
} catch (Exception e) { } catch (Exception e) {
myVersion = "UNKNOWN"; myVersion = "4.0.0";
} }
} }

View File

@ -46,7 +46,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT; private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
private FhirContext myContext; private FhirContext myContext;
private Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory>(); private Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<>();
private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE; private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT; private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
private String myProxyUsername; private String myProxyUsername;
@ -82,9 +82,6 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
/** /**
* Return the proxy username to authenticate with the HTTP proxy * Return the proxy username to authenticate with the HTTP proxy
*
* @param The
* proxy username
*/ */
protected String getProxyUsername() { protected String getProxyUsername() {
return myProxyUsername; return myProxyUsername;
@ -92,9 +89,6 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
/** /**
* Return the proxy password to authenticate with the HTTP proxy * Return the proxy password to authenticate with the HTTP proxy
*
* @param The
* proxy password
*/ */
protected String getProxyPassword() { protected String getProxyPassword() {
return myProxyPassword; return myProxyPassword;
@ -128,8 +122,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) { private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) {
T proxy = (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler); return (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
return proxy;
} }
/** /**
@ -162,9 +155,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
myInvocationHandlers.put(theClientType, invocationHandler); myInvocationHandlers.put(theClientType, invocationHandler);
} }
T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler(this)); return instantiateProxy(theClientType, invocationHandler.newInvocationHandler(this));
return proxy;
} }
/** /**
@ -331,13 +322,14 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
FhirVersionEnum serverFhirVersionEnum = null; FhirVersionEnum serverFhirVersionEnum = null;
if (StringUtils.isBlank(serverFhirVersionString)) { if (StringUtils.isBlank(serverFhirVersionString)) {
// we'll be lenient and accept this // we'll be lenient and accept this
ourLog.debug("Server conformance statement does not indicate the FHIR version");
} else { } else {
if (serverFhirVersionString.startsWith("0.4") || serverFhirVersionString.startsWith("0.5") || serverFhirVersionString.startsWith("1.0.")) { if (serverFhirVersionString.equals(FhirVersionEnum.DSTU2.getFhirVersionString())) {
serverFhirVersionEnum = FhirVersionEnum.DSTU2; serverFhirVersionEnum = FhirVersionEnum.DSTU2;
} else if (serverFhirVersionString.startsWith("3.0.")) { } else if (serverFhirVersionString.equals(FhirVersionEnum.DSTU2_1.getFhirVersionString())) {
serverFhirVersionEnum = FhirVersionEnum.DSTU2_1;
} else if (serverFhirVersionString.equals(FhirVersionEnum.DSTU3.getFhirVersionString())) {
serverFhirVersionEnum = FhirVersionEnum.DSTU3; serverFhirVersionEnum = FhirVersionEnum.DSTU3;
} else if (serverFhirVersionString.startsWith("3.1.")) {
serverFhirVersionEnum = FhirVersionEnum.R4;
} else { } else {
// we'll be lenient and accept this // we'll be lenient and accept this
ourLog.debug("Server conformance statement indicates unknown FHIR version: {}", serverFhirVersionString); ourLog.debug("Server conformance statement indicates unknown FHIR version: {}", serverFhirVersionString);

View File

@ -330,8 +330,9 @@ public class RestHookSubscriptionDstu2Interceptor extends BaseRestHookSubscripti
Subscription subscription = (Subscription) theResource; Subscription subscription = (Subscription) theResource;
if (subscription.getChannel() != null if (subscription.getChannel() != null
&& subscription.getChannel().getTypeElement().getValueAsEnum() == SubscriptionChannelTypeEnum.REST_HOOK && subscription.getChannel().getTypeElement().getValueAsEnum() == SubscriptionChannelTypeEnum.REST_HOOK
&& subscription.getStatusElement().getValueAsEnum() == SubscriptionStatusEnum.ACTIVE) { && subscription.getStatusElement().getValueAsEnum() == SubscriptionStatusEnum.REQUESTED) {
removeLocalSubscription(subscription.getIdElement().getIdPart()); removeLocalSubscription(subscription.getIdElement().getIdPart());
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
myRestHookSubscriptions.add(subscription); myRestHookSubscriptions.add(subscription);
ourLog.info("Subscription was added. Id: " + subscription.getId()); ourLog.info("Subscription was added. Id: " + subscription.getId());
} }

View File

@ -319,8 +319,9 @@ public class RestHookSubscriptionDstu3Interceptor extends BaseRestHookSubscripti
Subscription subscription = (Subscription) theResource; Subscription subscription = (Subscription) theResource;
if (subscription.getChannel() != null if (subscription.getChannel() != null
&& subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK && subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK
&& subscription.getStatus() == Subscription.SubscriptionStatus.ACTIVE) { && subscription.getStatus() == Subscription.SubscriptionStatus.REQUESTED) {
removeLocalSubscription(subscription.getIdElement().getIdPart()); removeLocalSubscription(subscription.getIdElement().getIdPart());
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
myRestHookSubscriptions.add(subscription); myRestHookSubscriptions.add(subscription);
ourLog.info("Subscription was added, id: {} - Have {}", subscription.getIdElement().getIdPart(), myRestHookSubscriptions.size()); ourLog.info("Subscription was added, id: {} - Have {}", subscription.getIdElement().getIdPart(), myRestHookSubscriptions.size());
} }

View File

@ -24,6 +24,7 @@ import java.util.*;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -98,7 +99,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
retVal.setPublisher(myPublisher); retVal.setPublisher(myPublisher);
retVal.setDate(conformanceDate()); retVal.setDate(conformanceDate());
retVal.setFhirVersion("0.0.82-3059"); // TODO: pull from model retVal.setFhirVersion(FhirVersionEnum.DSTU1.getFhirVersionString());
retVal.setAcceptUnknown(false); // TODO: make this configurable - this is a fairly big effort since the parser needs to be modified to actually allow it retVal.setAcceptUnknown(false); // TODO: make this configurable - this is a fairly big effort since the parser needs to be modified to actually allow it
retVal.getImplementation().setDescription(myServerConfiguration.getImplementationDescription()); retVal.getImplementation().setDescription(myServerConfiguration.getImplementationDescription());

View File

@ -28,6 +28,7 @@ import java.util.Map.Entry;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -162,7 +163,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
retVal.setPublisher(myPublisher); retVal.setPublisher(myPublisher);
retVal.setDate(conformanceDate()); retVal.setDate(conformanceDate());
retVal.setFhirVersion("1.0.2"); // TODO: pull from model retVal.setFhirVersion(FhirVersionEnum.DSTU2.getFhirVersionString());
retVal.setAcceptUnknown(UnknownContentCodeEnum.UNKNOWN_EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the parser retVal.setAcceptUnknown(UnknownContentCodeEnum.UNKNOWN_EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the parser
// needs to be modified to actually allow it // needs to be modified to actually allow it

View File

@ -68,7 +68,7 @@ public class ClientServerValidationDstu2Test {
@Test @Test
public void testClientUsesInterceptors() throws Exception { public void testClientUsesInterceptors() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();
conf.setFhirVersion("0.5.0"); conf.setFhirVersion("1.0.2");
final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -110,7 +110,7 @@ public class ClientServerValidationDstu2Test {
@Test @Test
public void testForceConformanceCheck() throws Exception { public void testForceConformanceCheck() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();
conf.setFhirVersion("0.5.0"); conf.setFhirVersion("1.0.2");
final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -254,6 +254,44 @@ public class ClientServerValidationDstu2Test {
verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class)); verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class));
} }
@Test
public void testServerReturnsAppropriateVersionForDstu2() throws Exception {
Conformance conf = new Conformance();
conf.setFhirVersion("1.0.2");
final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myFirstResponse) {
myFirstResponse = false;
return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(new Patient())), Charset.forName("UTF-8"));
}
}
});
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
IGenericClient client = myCtx.newRestfulGenericClient("http://foo");
// don't load the conformance until the first time the client is actually used
assertTrue(myFirstResponse);
client.read(new UriDt("http://foo/Patient/123"));
assertFalse(myFirstResponse);
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
// Conformance only loaded once, then 3 reads
verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class));
}
@Test @Test
public void testServerReturnsWrongVersionForDstu2() throws Exception { public void testServerReturnsWrongVersionForDstu2() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();

View File

@ -30,6 +30,7 @@ import java.util.jar.Manifest;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.*; import org.hl7.fhir.instance.model.*;
import org.hl7.fhir.instance.model.Conformance.*; import org.hl7.fhir.instance.model.Conformance.*;
@ -158,7 +159,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
retVal.setPublisher(myPublisher); retVal.setPublisher(myPublisher);
retVal.setDate(conformanceDate()); retVal.setDate(conformanceDate());
retVal.setFhirVersion("1.0.2"); // TODO: pull from model retVal.setFhirVersion(FhirVersionEnum.DSTU2_HL7ORG.getFhirVersionString());
retVal.setAcceptUnknown(UnknownContentCode.EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the parser retVal.setAcceptUnknown(UnknownContentCode.EXTENSIONS); // TODO: make this configurable - this is a fairly big effort since the parser
// needs to be modified to actually allow it // needs to be modified to actually allow it

View File

@ -57,46 +57,9 @@ public class ClientServerValidationTestHl7OrgDstu2 {
} }
@Test @Test
public void testServerReturnsAppropriateVersionForDstu2_040() throws Exception { public void testServerReturnsAppropriateVersionForDstu2() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();
conf.setFhirVersion("0.5.0"); conf.setFhirVersion("1.0.2");
final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myFirstResponse) {
myFirstResponse=false;
return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(new Patient())), Charset.forName("UTF-8"));
}
}});
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
myCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.ONCE);
IGenericClient client = myCtx.newRestfulGenericClient("http://foo");
// don't load the conformance until the first time the client is actually used
assertTrue(myFirstResponse);
client.read(new UriDt("http://foo/Patient/123"));
assertFalse(myFirstResponse);
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
// Conformance only loaded once, then 3 reads
verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class));
}
@Test
public void testServerReturnsAppropriateVersionForDstu2_050() throws Exception {
Conformance conf = new Conformance();
conf.setFhirVersion("0.5.0");
final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -133,7 +96,7 @@ public class ClientServerValidationTestHl7OrgDstu2 {
@Test @Test
public void testServerReturnsWrongVersionForDstu2() throws Exception { public void testServerReturnsWrongVersionForDstu2() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();
conf.setFhirVersion("0.80"); conf.setFhirVersion("0.0.82");
String msg = myCtx.newXmlParser().encodeResourceToString(conf); String msg = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -150,7 +113,7 @@ public class ClientServerValidationTestHl7OrgDstu2 {
fail(); fail();
} catch (FhirClientInappropriateForServerException e) { } catch (FhirClientInappropriateForServerException e) {
String out = e.toString(); String out = e.toString();
String want = "The server at base URL \"http://foo/metadata\" returned a conformance statement indicating that it supports FHIR version \"0.80\" which corresponds to DSTU1, but this client is configured to use DSTU2_HL7ORG (via the FhirContext)"; String want = "The server at base URL \"http://foo/metadata\" returned a conformance statement indicating that it supports FHIR version \"0.0.82\" which corresponds to DSTU1, but this client is configured to use DSTU2_HL7ORG (via the FhirContext)";
ourLog.info(out); ourLog.info(out);
ourLog.info(want); ourLog.info(want);
assertThat(out, containsString(want)); assertThat(out, containsString(want));

View File

@ -87,6 +87,7 @@ public class ClientServerValidationDstu1Test {
} }
@Test @Test
@Ignore
public void testServerReturnsWrongVersionDstu() throws Exception { public void testServerReturnsWrongVersionDstu() throws Exception {
CapabilityStatement conf = new CapabilityStatement(); CapabilityStatement conf = new CapabilityStatement();
conf.setFhirVersion("0.4.0"); conf.setFhirVersion("0.4.0");

View File

@ -350,6 +350,14 @@
<id>dconlan</id> <id>dconlan</id>
<name>dconlan</name> <name>dconlan</name>
</developer> </developer>
<developer>
<id>psbrandt</id>
<name>Pascal Brandt</name>
</developer>
<developer>
<id>InfiniteLoop90</id>
<name>Clayton Bodendein</name>
</developer>
</developers> </developers>
<licenses> <licenses>

View File

@ -283,6 +283,19 @@
Extensions on ID datatypes were not parsed or serialized correctly. Thanks to Extensions on ID datatypes were not parsed or serialized correctly. Thanks to
Stephen Rivière for the pull request! Stephen Rivière for the pull request!
</action> </action>
<action type="fix" issue="710">
Fix a bug in REST Hook Subscription interceptors which prevented subscriptions
from being activated. Thanks to Jeff Chung for the pull request!
</action>
<action type="fix" issue="708">
Fix broken links in usage pattern diagram on website. Thanks to
Pascal Brandt for the pull request!
</action>
<action type="fix" issue="706">
Fix incorrect FHIR Version Strings that were being outputted and verified in the
client for some versions of FHIR. Thanks to Clayton Bodendein for the
pull request!
</action>
</release> </release>
<release version="2.5" date="2017-06-08"> <release version="2.5" date="2017-06-08">
<action type="fix"> <action type="fix">