diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 9d39212b6453..8074aa23b7e6 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -35,10 +35,6 @@ commons-codec commons-codec - - org.codehaus.woodstox - woodstox-core-asl - @@ -59,6 +55,11 @@ 2.1-SNAPSHOT true + + org.codehaus.woodstox + woodstox-core-asl + true + org.apache.httpcomponents httpclient-android @@ -75,10 +76,6 @@ commons-io commons-io - - commons-codec - commons-codec - org.apache.commons commons-lang3 @@ -98,25 +95,59 @@ org.apache.maven.plugins maven-failsafe-plugin - + dstu2_shade integration-test verify + + + **/*Dstu2ShadeIT.java + + + ${basedir}/target/hapi-fhir-android-${project.version}-dstu2.jar + + + ca.uhn.hapi.fhir:* + org.codehaus.woodstox:woodstox-core-asl + org.codehaus.woodstox:stax2-api + + + + + dstu2 + + integration-test + verify + + + + **/*Dstu2IT.java + + + org.codehaus.woodstox:woodstox-core-asl + org.codehaus.woodstox:stax2-api + + + + + dstu3 + + integration-test + verify + + + + **/*Dstu3IT.java + + + org.codehaus.woodstox:woodstox-core-asl + org.codehaus.woodstox:stax2-api + + @@ -130,6 +161,9 @@ ca.uhn.hapi.fhir:hapi-fhir-base + org.codehaus.woodstox:woodstox-core-asl + javax.xml.stream:stax-api + org.codehaus.woodstox:stax2-api @@ -141,6 +175,10 @@ javax.json ca.uhn.fhir.repackage.javax.json + + com.ctc.wstx.stax + ca.uhn.fhir.repackage.com.ctc.wstx.stax + @@ -182,8 +220,8 @@ package shade - - + + dstu @@ -261,6 +299,27 @@ + + org.jacoco + jacoco-maven-plugin + + + ${basedir}/target/classes + + + ${basedir}/src/main/java + + true + + + + default-prepare-agent + + prepare-agent + + + + diff --git a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2IT.java b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2IT.java new file mode 100644 index 000000000000..2ac7845d43b5 --- /dev/null +++ b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2IT.java @@ -0,0 +1,192 @@ +package ca.uhn.fhir.android; + +import static org.junit.Assert.*; + +import java.io.File; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.naming.ConfigurationException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.dstu2.composite.QuantityDt; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; + +public class BuiltJarDstu2IT { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarDstu2IT.class); + + @BeforeClass + public static void beforeClass() { + System.setProperty("javax.xml.stream.XMLInputFactory", "FOO"); + System.setProperty("javax.xml.stream.XMLOutputFactory", "FOO"); + } + + @Test + public void testParserXml() throws Exception { + + FhirContext ctx = FhirContext.forDstu2(); + + Patient p = new Patient(); + p.addIdentifier().setSystem("system"); + + try { + ctx.newXmlParser().encodeResourceToString(p); + fail(); + } catch (ca.uhn.fhir.context.ConfigurationException e) { + assertEquals("Unable to initialize StAX - XML processing is disabled",e.getMessage()); + } + } + + @Test + public void testParserJson() { + + FhirContext ctx = FhirContext.forDstu2(); + + Observation o = new Observation(); + o.getCode().setText("TEXT"); + o.setValue(new QuantityDt(123)); + o.addIdentifier().setSystem("system"); + + String str = ctx.newJsonParser().encodeResourceToString(o); + Observation p2 = ctx.newJsonParser().parseResource(Observation.class, str); + + assertEquals("TEXT", p2.getCode().getText()); + + QuantityDt dt = (QuantityDt) p2.getValue(); + dt.getComparatorElement().getValueAsEnum(); + + } + + /** + * A simple client test - We try to connect to a server that doesn't exist, but + * if we at least get the right exception it means we made it up to the HTTP/network stack + * + * Disabled for now - TODO: add the old version of the apache client (the one that + * android uses) and see if this passes + */ + @SuppressWarnings("deprecation") + public void testClient() { + FhirContext ctx = FhirContext.forDstu2(); + try { + IGenericClient client = ctx.newRestfulGenericClient("http://127.0.0.1:44442/SomeBase"); + client.conformance(); + } catch (FhirClientConnectionException e) { + // this is good + } + } + + /** + * Android does not like duplicate entries in the JAR + */ + @Test + public void testJarContents() throws Exception { + String wildcard = "hapi-fhir-android-*.jar"; + Collection files = FileUtils.listFiles(new File("target"), new WildcardFileFilter(wildcard), null); + if (files.isEmpty()) { + throw new Exception("No files matching " + wildcard); + } + + for (File file : files) { + if (file.getName().endsWith("sources.jar")) { + continue; + } + if (file.getName().endsWith("javadoc.jar")) { + continue; + } + if (file.getName().contains("original.jar")) { + continue; + } + + ourLog.info("Testing file: {}", file); + + ZipFile zip = new ZipFile(file); + + int totalClasses = 0; + int totalMethods = 0; + TreeSet topMethods = new TreeSet(); + + try { + Set names = new HashSet(); + for (Enumeration iter = zip.entries(); iter.hasMoreElements();) { + ZipEntry next = iter.nextElement(); + String nextName = next.getName(); + if (!names.add(nextName)) { + throw new Exception("File " + file + " contains duplicate contents: " + nextName); + } + + if (nextName.contains("$") == false) { + if (nextName.endsWith(".class")) { + String className = nextName.replace("/", ".").replace(".class", ""); + try { + Class clazz = Class.forName(className); + int methodCount = clazz.getMethods().length; + topMethods.add(new ClassMethodCount(className, methodCount)); + totalClasses++; + totalMethods += methodCount; + } catch (NoClassDefFoundError e) { + // ignore + } catch (ClassNotFoundException e) { + // ignore + } + } + } + } + + ourLog.info("File {} contains {} entries", file, names.size()); + ourLog.info("Total classes {} - Total methods {}", totalClasses, totalMethods); + ourLog.info("Top classes {}", new ArrayList(topMethods).subList(Math.max(0, topMethods.size() - 10), topMethods.size())); + + } finally { + zip.close(); + } + } + } + + private static class ClassMethodCount implements Comparable { + + private String myClassName; + private int myMethodCount; + + public ClassMethodCount(String theClassName, int theMethodCount) { + myClassName = theClassName; + myMethodCount = theMethodCount; + } + + @Override + public String toString() { + return myClassName + "[" + myMethodCount + "]"; + } + + @Override + public int compareTo(ClassMethodCount theO) { + return myMethodCount - theO.myMethodCount; + } + + public String getClassName() { + return myClassName; + } + + public void setClassName(String theClassName) { + myClassName = theClassName; + } + + public int getMethodCount() { + return myMethodCount; + } + + public void setMethodCount(int theMethodCount) { + myMethodCount = theMethodCount; + } + + } + +} diff --git a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2ShadeIT.java similarity index 85% rename from hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java rename to hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2ShadeIT.java index f14c6534298c..89776450c80a 100644 --- a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java +++ b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarDstu2ShadeIT.java @@ -19,52 +19,41 @@ import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; -public class BuiltJarIT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarIT.class); +public class BuiltJarDstu2ShadeIT { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarDstu2ShadeIT.class); - @BeforeClass - public static void beforeClass() { - System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); - System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); - } - @Test public void testParserXml() throws Exception { - try { - Class.forName("com.ctc.wstx.stax.WstxOutputFactory"); - } catch (ClassNotFoundException e) { - return; - } - + FhirContext ctx = FhirContext.forDstu2(); - + Patient p = new Patient(); p.addIdentifier().setSystem("system"); - + String str = ctx.newXmlParser().encodeResourceToString(p); Patient p2 = ctx.newXmlParser().parseResource(Patient.class, str); - + assertEquals("system", p2.getIdentifierFirstRep().getSystemElement().getValueAsString()); } - + @Test public void testParserJson() { - + FhirContext ctx = FhirContext.forDstu2(); - + Observation o = new Observation(); o.getCode().setText("TEXT"); o.setValue(new QuantityDt(123)); o.addIdentifier().setSystem("system"); - + String str = ctx.newJsonParser().encodeResourceToString(o); Observation p2 = ctx.newJsonParser().parseResource(Observation.class, str); - + assertEquals("TEXT", p2.getCode().getText()); - + QuantityDt dt = (QuantityDt) p2.getValue(); dt.getComparatorElement().getValueAsEnum(); - + } /** @@ -83,7 +72,7 @@ public void testClient() { // this is good } } - + /** * Android does not like duplicate entries in the JAR */ @@ -105,15 +94,15 @@ public void testJarContents() throws Exception { if (file.getName().contains("original.jar")) { continue; } - + ourLog.info("Testing file: {}", file); ZipFile zip = new ZipFile(file); - + int totalClasses = 0; int totalMethods = 0; TreeSet topMethods = new TreeSet(); - + try { Set names = new HashSet(); for (Enumeration iter = zip.entries(); iter.hasMoreElements();) { @@ -122,16 +111,16 @@ public void testJarContents() throws Exception { if (!names.add(nextName)) { throw new Exception("File " + file + " contains duplicate contents: " + nextName); } - + if (nextName.contains("$") == false) { if (nextName.endsWith(".class")) { String className = nextName.replace("/", ".").replace(".class", ""); try { - Class clazz = Class.forName(className); - int methodCount = clazz.getMethods().length; - topMethods.add(new ClassMethodCount(className, methodCount)); - totalClasses++; - totalMethods += methodCount; + Class clazz = Class.forName(className); + int methodCount = clazz.getMethods().length; + topMethods.add(new ClassMethodCount(className, methodCount)); + totalClasses++; + totalMethods += methodCount; } catch (NoClassDefFoundError e) { // ignore } catch (ClassNotFoundException e) { @@ -140,11 +129,11 @@ public void testJarContents() throws Exception { } } } - + ourLog.info("File {} contains {} entries", file, names.size()); ourLog.info("Total classes {} - Total methods {}", totalClasses, totalMethods); - ourLog.info("Top classes {}", new ArrayList(topMethods).subList(Math.max(0,topMethods.size() - 10), topMethods.size())); - + ourLog.info("Top classes {}", new ArrayList(topMethods).subList(Math.max(0, topMethods.size() - 10), topMethods.size())); + } finally { zip.close(); } @@ -155,7 +144,7 @@ private static class ClassMethodCount implements Comparable { private String myClassName; private int myMethodCount; - + public ClassMethodCount(String theClassName, int theMethodCount) { myClassName = theClassName; myMethodCount = theMethodCount; @@ -186,7 +175,7 @@ public int getMethodCount() { public void setMethodCount(int theMethodCount) { myMethodCount = theMethodCount; } - + } - + } diff --git a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java new file mode 100644 index 000000000000..c44b29349175 --- /dev/null +++ b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java @@ -0,0 +1,996 @@ +package ca.uhn.fhir.android.client; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.junit.*; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.primitive.UriDt; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; +import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; +import ca.uhn.fhir.rest.client.interceptor.UserInfoInterceptor; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; +import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +import ca.uhn.fhir.util.VersionUtil; + +public class GenericClientDstu3IT { + + + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu3IT.class); + private int myAnswerCount; + private HttpClient myHttpClient; + private HttpResponse myHttpResponse; + + @Before + public void before() { + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + myAnswerCount = 0; + + } + + private String expectedUserAgent() { + return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.DSTU3.getFhirVersionString() + "/DSTU3; apache)"; + } + + + private String extractBodyAsString(ArgumentCaptor capt) throws IOException { + String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), "UTF-8"); + return body; + } + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testBinaryCreateWithFhirContentType() throws Exception { + IParser p = ourCtx.newXmlParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.getText().setDivAsString("A PATIENT"); + + Binary bin = new Binary(); + bin.setContent(ourCtx.newJsonParser().encodeResourceToString(pt).getBytes("UTF-8")); + bin.setContentType(Constants.CT_FHIR_JSON); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)); + assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); + + Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8")); + assertEquals("
A PATIENT
", outputPt.getText().getDivAsString()); + } + + /** + * See #150 + */ + @Test + public void testNullAndEmptyParamValuesAreIgnored() throws Exception { + ArgumentCaptor capt = prepareClientForSearchResponse(); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + //@formatter:off + client + .search() + .forResource(Patient.class) + .where(Patient.FAMILY.matches().value((String)null)) + .and(Patient.BIRTHDATE.exactly().day((Date)null)) + .and(Patient.GENDER.exactly().code((String)null)) + .and(Patient.ORGANIZATION.hasId((String)null)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + } + + + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testBinaryCreateWithNoContentType() throws Exception { + IParser p = ourCtx.newJsonParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); + + } + + @SuppressWarnings("unchecked") + @Test + public void testClientFailures() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenThrow(IllegalStateException.class, RuntimeException.class, Exception.class); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (FhirClientConnectionException e) { + assertEquals("java.lang.IllegalStateException", e.getMessage()); + } + + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (RuntimeException e) { + assertEquals("java.lang.RuntimeException", e.toString()); + } + + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (FhirClientConnectionException e) { + assertEquals("java.lang.Exception", e.getMessage()); + } + } + + + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testCreateWithPreferRepresentationServerReturnsResource() throws Exception { + final IParser p = ourCtx.newJsonParser(); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + myAnswerCount++; + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.create().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(1, myAnswerCount); + assertNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + + + + + + + @Test + public void testForceConformance() throws Exception { + final IParser p = ourCtx.newJsonParser(); + + final Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final Patient patient = new Patient(); + patient.addName().addFamily("FAM"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + private int myCount = 0; + + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + final String respString; + if (myCount == 1 || myCount == 2) { + ourLog.info("Encoding patient"); + respString = p.encodeResourceToString(patient); + } else { + ourLog.info("Encoding conformance"); + respString = p.encodeResourceToString(conf); + } + myCount++; + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); + IGenericClient client = ourCtx.newRestfulGenericClient("http://testForceConformance.com/fhir"); + + client.read().resource("Patient").withId("1").execute(); + assertEquals(2, capt.getAllValues().size()); + assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString()); + assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(1).getURI().toASCIIString()); + + client.read().resource("Patient").withId("1").execute(); + assertEquals(3, capt.getAllValues().size()); + assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(2).getURI().toASCIIString()); + + client.forceConformanceCheck(); + assertEquals(4, capt.getAllValues().size()); + assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(3).getURI().toASCIIString()); + } + + @Test + public void testHttp499() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 499, "Wacky Message")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader("HELLO"), StandardCharsets.UTF_8); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (UnclassifiedServerFailureException e) { + assertEquals("ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException: HTTP 499 Wacky Message", e.toString()); + assertEquals("HELLO", e.getResponseBody()); + } + + } + + @Test + public void testHttp501() throws Exception { + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 501, "Not Implemented")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader("not implemented"), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (NotImplementedOperationException e) { + assertEquals("HTTP 501 Not Implemented", e.getMessage()); + } + + } + + @SuppressWarnings("deprecation") + @Test + public void testInvalidConformanceCall() { + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + try { + client.conformance(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Must call fetchConformance() instead of conformance() for RI/STU3+ structures", e.getMessage()); + } + } + + + + @Test + public void testPutDoesntForceAllIdsJson() throws Exception { + IParser p = ourCtx.newJsonParser(); + + Patient patient = new Patient(); + patient.setId("PATIENT1"); + patient.addName().addFamily("PATIENT1"); + + Bundle bundle = new Bundle(); + bundle.setId("BUNDLE1"); + bundle.addEntry().setResource(patient); + + final String encoded = p.encodeResourceToString(bundle); + assertEquals("{\"resourceType\":\"Bundle\",\"id\":\"BUNDLE1\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"PATIENT1\",\"name\":[{\"family\":[\"PATIENT1\"]}]}}]}", encoded); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + //@formatter:off + client + .update() + .resource(bundle) + .prefer(PreferReturnEnum.REPRESENTATION) + .encodedJson() + .execute(); + //@formatter:on + + HttpPut httpRequest = (HttpPut) capt.getValue(); + assertEquals("http://example.com/fhir/Bundle/BUNDLE1?_format=json", httpRequest.getURI().toASCIIString()); + + String requestString = IOUtils.toString(httpRequest.getEntity().getContent(), StandardCharsets.UTF_8); + assertEquals(encoded, requestString); + } + + @Test + public void testResponseHasContentTypeMissing() throws Exception { + IParser p = ourCtx.newJsonParser(); + Patient patient = new Patient(); + patient.addName().addFamily("FAM"); + final String respString = p.encodeResourceToString(patient); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(null); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (NonFhirResponseException e) { + assertEquals("Response contains no Content-Type", e.getMessage()); + } + + // Patient resp = client.read().resource(Patient.class).withId("1").execute(); + // assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString()); + } + + @Test + public void testResponseHasContentTypeNonFhir() throws Exception { + IParser p = ourCtx.newJsonParser(); + Patient patient = new Patient(); + patient.addName().addFamily("FAM"); + final String respString = p.encodeResourceToString(patient); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "text/plain")); + // when(myHttpResponse.getEntity().getContentType()).thenReturn(null); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + try { + client.read().resource(Patient.class).withId("1").execute(); + fail(); + } catch (NonFhirResponseException e) { + assertEquals("Response contains non FHIR Content-Type 'text/plain' : {\"resourceType\":\"Patient\",\"name\":[{\"family\":[\"FAM\"]}]}", e.getMessage()); + } + + // Patient resp = client.read().resource(Patient.class).withId("1").execute(); + // assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString()); + } + + @Test + public void testSearchByDate() throws Exception { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + DateTimeDt now = DateTimeDt.withCurrentTime(); + String dateString = now.getValueAsString().substring(0, 10); + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.BIRTHDATE.after().day(dateString)) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?birthdate=gt"+dateString + "&_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + } + + + @Test + public void testSearchByString() throws Exception { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("AAA")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value(new StringDt("AAA"))) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matches().values("AAA", "BBB")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + idx++; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matches().values(Arrays.asList("AAA", "BBB"))) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + idx++; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matchesExactly().value("AAA")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + //@formatter:off + client.search() + .forResource("Patient") + .where(Patient.NAME.matchesExactly().value(new StringDt("AAA"))) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString()); + idx++; + + } + + @Test + public void testSearchByUrl() throws Exception { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + //@formatter:off + client.search() + .forResource("Device") + .where(Device.URL.matches().value("http://foo.com")) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Device?url=http://foo.com&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + idx++; + + } + + @Test + public void testAcceptHeaderWithEncodingSpecified() throws Exception { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + //@formatter:off + client.setEncoding(EncodingEnum.JSON); + client.search() + .forResource("Device") + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue()); + idx++; + + //@formatter:off + client.setEncoding(EncodingEnum.XML); + client.search() + .forResource("Device") + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue()); + idx++; + + } + + @Test + public void testSearchForUnknownType() throws Exception { + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + try { + client.search(new UriDt("?aaaa")); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Unable to determine the resource type from the given URI: ?aaaa", e.getMessage()); + } + } + + private ArgumentCaptor prepareClientForSearchResponse() throws IOException, ClientProtocolException { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + return capt; + } + + + + @Test + public void testTransactionWithInvalidBody() throws Exception { + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + // Transaction + try { + client.transaction().withBundle("FOO"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage()); + } + + // Create + try { + client.create().resource("FOO").execute(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage()); + } + + // Update + try { + client.update().resource("FOO").execute(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage()); + } + + // Validate + try { + client.validate().resource("FOO").execute(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage()); + } + + + } + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testUpdateById() throws Exception { + IParser p = ourCtx.newJsonParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("222"); + pt.getText().setDivAsString("A PATIENT"); + + client.update().resource(pt).withId("111").execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + String body = extractBodyAsString(capt); + assertThat(body, containsString("")); + } + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testUpdateWithPreferRepresentationServerReturnsOO() throws Exception { + final IParser p = ourCtx.newJsonParser(); + + final OperationOutcome resp0 = new OperationOutcome(); + resp0.getText().setDivAsString("OK!"); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + if (myAnswerCount++ == 0) { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + } else { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("Patient/222"); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(2, myAnswerCount); + assertNotNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
OK!
", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString()); + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(0).getURI().toASCIIString()); + assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString()); + } + + @Test + public void testUpdateWithPreferRepresentationServerReturnsResource() throws Exception { + final IParser p = ourCtx.newJsonParser(); + + final Patient resp1 = new Patient(); + resp1.setActive(true); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + myAnswerCount++; + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("Patient/222"); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(1, myAnswerCount); + assertNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals(true, ((Patient) outcome.getResource()).getActive()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222?_format=json", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + + @Test + public void testUserAgentForConformance() throws Exception { + IParser p = ourCtx.newJsonParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + client.fetchConformance().ofType(Conformance.class).execute(); + assertEquals("http://example.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + } + + + /** + * TODO: narratives don't work without stax + */ + @Test + @Ignore + public void testValidate() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + final OperationOutcome resp0 = new OperationOutcome(); + resp0.getText().setDivAsString("OK!"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] {}; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("Patient/222"); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.validate().resource(pt).execute(); + + assertNotNull(outcome.getOperationOutcome()); + assertEquals("
OK!
", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString()); + + } + + private void validateUserAgent(ArgumentCaptor capt) { + assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); + assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() { + + // Force StAX to fail like it will on android + System.setProperty("javax.xml.stream.XMLInputFactory", "FOO"); + System.setProperty("javax.xml.stream.XMLOutputFactory", "FOO"); + + ourCtx = FhirContext.forDstu3(); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index f0a1a32d277d..3bfb5bee98cb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.rest.client; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR - Core Library @@ -22,33 +20,20 @@ * #L% */ -import java.io.ByteArrayInputStream; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; +import org.hl7.fhir.instance.model.api.*; + +import ca.uhn.fhir.context.*; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.SummaryEnum; @@ -67,6 +52,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.XmlUtil; public abstract class BaseClient implements IRestfulClient { @@ -105,6 +91,11 @@ public abstract class BaseClient implements IRestfulClient { if ("true".equals(System.getProperty(HAPI_CLIENT_KEEPRESPONSES))) { setKeepResponses(true); } + + if (XmlUtil.isStaxPresent() == false) { + myEncoding = EncodingEnum.JSON; + } + } protected Map> createExtraParams() { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 22a17a003abd..8c3f4e86963f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -63,6 +63,8 @@ public class Constants { public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; public static final String HEADER_ACCEPT_VALUE_XML_OR_JSON_LEGACY = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_JSON + ";q=1.0"; public static final String HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY = CT_FHIR_XML_NEW + ";q=1.0, " + CT_FHIR_JSON_NEW + ";q=1.0, " + HEADER_ACCEPT_VALUE_XML_OR_JSON_LEGACY.replace("1.0", "0.9"); + public static final String HEADER_ACCEPT_VALUE_XML_NON_LEGACY = CT_FHIR_XML_NEW + ";q=1.0, " + CT_FHIR_XML + ";q=0.9"; + public static final String HEADER_ACCEPT_VALUE_JSON_NON_LEGACY = CT_FHIR_JSON_NEW + ";q=1.0, " + CT_FHIR_JSON + ";q=0.9"; public static final String HEADER_ALLOW = "Allow"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic "; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 0fcf91941637..de786b8b1569 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -787,9 +787,17 @@ public static void addAcceptHeaderToRequest(EncodingEnum theEncoding, IHttpReque theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY); } } else if (theEncoding == EncodingEnum.JSON) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); + if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2) == false) { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); + } else { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY); + } } else if (theEncoding == EncodingEnum.XML) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML); + if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2) == false) { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML); + } else { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY); + } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java index 98f625792103..55d6cf74e70b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java @@ -19,26 +19,12 @@ * limitations under the License. * #L% */ - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; +import java.io.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import javax.xml.stream.FactoryConfigurationError; -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLEventWriter; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLResolver; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; +import javax.xml.stream.*; import org.apache.commons.lang3.StringEscapeUtils; import org.codehaus.stax2.XMLOutputFactory2; @@ -47,6 +33,7 @@ import com.ctc.wstx.api.WstxInputProperties; import com.ctc.wstx.stax.WstxOutputFactory; +import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.util.jar.DependencyLogFactory; import ca.uhn.fhir.util.jar.IDependencyLog; @@ -62,6 +49,7 @@ public class XmlUtil { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class); private static Throwable ourNextException; private static volatile XMLOutputFactory ourOutputFactory; + private static Boolean ourStaxPresent; private static final Map VALID_ENTITY_NAMES; private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver(); @@ -1528,8 +1516,8 @@ private static XMLOutputFactory createOutputFactory() throws FactoryConfiguratio // ok } - XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); - + XMLOutputFactory outputFactory = newOutputFactory(); + if (!ourHaveLoggedStaxImplementation) { logStaxImplementation(outputFactory.getClass()); } @@ -1601,8 +1589,7 @@ private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigura // ok } - XMLInputFactory inputFactory; - inputFactory = XMLInputFactory.newInstance(); + XMLInputFactory inputFactory = newInputFactory(); if (!ourHaveLoggedStaxImplementation) { logStaxImplementation(inputFactory.getClass()); @@ -1645,7 +1632,6 @@ private static XMLInputFactory getOrCreateInputFactory() throws FactoryConfigura return ourInputFactory; } - private static XMLOutputFactory getOrCreateOutputFactory() throws FactoryConfigurationError { if (ourOutputFactory == null) { ourOutputFactory = createOutputFactory(); @@ -1661,6 +1647,29 @@ private static void logStaxImplementation(Class theClass) { ourHaveLoggedStaxImplementation = true; } + + static XMLInputFactory newInputFactory() throws FactoryConfigurationError { + XMLInputFactory inputFactory; + try { + inputFactory = XMLInputFactory.newInstance(); + throwUnitTestExceptionIfConfiguredToDoSo(); + } catch (Throwable e) { + throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); + } + return inputFactory; + } + + static XMLOutputFactory newOutputFactory() throws FactoryConfigurationError { + XMLOutputFactory outputFactory; + try { + outputFactory = XMLOutputFactory.newInstance(); + throwUnitTestExceptionIfConfiguredToDoSo(); + } catch (Throwable e) { + throw new ConfigurationException("Unable to initialize StAX - XML processing is disabled", e); + } + return outputFactory; + } + /** * FOR UNIT TESTS ONLY - Throw this exception for the next operation */ @@ -1691,6 +1700,26 @@ public Object resolveEntity(String thePublicID, String theSystemID, String theBa } } + /** + * This method will return true if a StAX XML parsing library is present + * on the classpath + */ + public static boolean isStaxPresent() { + Boolean retVal = ourStaxPresent; + if (retVal == null) { + try { + newInputFactory(); + ourStaxPresent = Boolean.TRUE; + retVal = Boolean.TRUE; + } catch (ConfigurationException e) { + ourLog.info("StAX not detected on classpath, XML processing will be disabled"); + ourStaxPresent = Boolean.FALSE; + retVal = Boolean.FALSE; + } + } + return retVal; + } + public static class MyEscaper implements EscapingWriterFactory { @Override diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 508e0733b333..f84e913f1131 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -232,6 +232,7 @@ hapi-fhir-structures-dstu3/target/jacoco.exec hapi-fhir-jpaserver-base/target/jacoco.exec hapi-fhir-client-okhttp/target/jacoco.exec + hapi-fhir-android/target/jacoco.exec diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java index 2605b3c65ef1..c24b554f6ffc 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java @@ -1644,7 +1644,7 @@ public void testSearchByPostUseJson() throws Exception { assertEquals("name=james", ourRequestBodyString); assertEquals("application/x-www-form-urlencoded", ourRequestContentType); - assertEquals(Constants.CT_FHIR_JSON, ourRequestFirstHeaders.get("Accept").getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, ourRequestFirstHeaders.get("Accept").getValue()); } @Test diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/util/XmlUtilTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/util/XmlUtilTest.java index 52c0828c7e1e..f2ecfcf9353a 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/util/XmlUtilTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/util/XmlUtilTest.java @@ -1,14 +1,45 @@ package ca.uhn.fhir.util; +import static org.junit.Assert.*; + import java.io.StringReader; import java.io.StringWriter; +import javax.xml.stream.FactoryConfigurationError; + +import org.junit.After; import org.junit.AfterClass; import org.junit.Test; public class XmlUtilTest { - + @Test + public void testCreateInputFactoryWithException() { + XmlUtil.setThrowExceptionForUnitTest(new Error("FOO ERROR")); + try { + XmlUtil.newInputFactory(); + fail(); + } catch (Exception e) { + assertEquals("Unable to initialize StAX - XML processing is disabled", e.getMessage()); + } + } + + @Test + public void testCreateOutputFactoryWithException() { + XmlUtil.setThrowExceptionForUnitTest(new Error("FOO ERROR")); + try { + XmlUtil.newOutputFactory(); + fail(); + } catch (Exception e) { + assertEquals("Unable to initialize StAX - XML processing is disabled", e.getMessage()); + } + } + + @After + public void after() { + XmlUtil.setThrowExceptionForUnitTest(null); + } + @Test public void testCreateReader() throws Exception { XmlUtil.createXmlReader(new StringReader("")); @@ -24,7 +55,6 @@ public void testCreateStreamWriter() throws Exception { XmlUtil.createXmlStreamWriter(new StringWriter()); } - @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java index 782b0979478b..1b4ddce962dc 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java @@ -67,7 +67,7 @@ public void testMimetypeXmlNew() throws Exception { assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); - assertEquals("application/xml+fhir", capt.getAllValues().get(0).getFirstHeader("accept").getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getFirstHeader("accept").getValue()); assertEquals("
A PATIENT
", extractBodyAsString(capt)); } @@ -87,7 +87,7 @@ public void testMimetypeXmlLegacy() throws Exception { assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); - assertEquals("application/xml+fhir", capt.getAllValues().get(0).getFirstHeader("accept").getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getFirstHeader("accept").getValue()); assertEquals("
A PATIENT
", extractBodyAsString(capt)); } @@ -107,7 +107,7 @@ public void testMimetypeJsonNew() throws Exception { assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); - assertEquals("application/json+fhir", capt.getAllValues().get(0).getFirstHeader("accept").getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getFirstHeader("accept").getValue()); assertEquals("{\"resourceType\":\"Patient\",\"text\":{\"div\":\"
A PATIENT
\"}}", extractBodyAsString(capt)); } @@ -127,7 +127,7 @@ public void testMimetypeJsonLegacy() throws Exception { assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); - assertEquals("application/json+fhir", capt.getAllValues().get(0).getFirstHeader("accept").getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getFirstHeader("accept").getValue()); assertEquals("{\"resourceType\":\"Patient\",\"text\":{\"div\":\"
A PATIENT
\"}}", extractBodyAsString(capt)); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java index 1238ab9c75f1..a08031fe81de 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -64,6 +64,7 @@ import ca.uhn.fhir.rest.client.interceptor.UserInfoInterceptor; import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; import ca.uhn.fhir.util.TestUtil; @@ -204,7 +205,7 @@ public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable validateUserAgent(capt); assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)); assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); @@ -296,7 +297,7 @@ public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable validateUserAgent(capt); assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); } @@ -1283,6 +1284,48 @@ public InputStream answer(InvocationOnMock theInvocation) throws Throwable { } + @Test + public void testAcceptHeaderWithEncodingSpecified() throws Exception { + final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).then(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + //@formatter:off + client.setEncoding(EncodingEnum.JSON); + client.search() + .forResource("Device") + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue()); + idx++; + + //@formatter:off + client.setEncoding(EncodingEnum.XML); + client.search() + .forResource("Device") + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString())); + assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue()); + idx++; + + } + @Test public void testSearchForUnknownType() throws Exception { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -1458,7 +1501,7 @@ public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable validateUserAgent(capt); assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); String body = extractBodyAsString(capt); assertThat(body, containsString("")); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0c2edf322b37..d9b88888a3bc 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -55,6 +55,13 @@ parent bundle were incorrectly given the ID of the parent. Thanks to Filip Domazet for reporting! + + STU clients now use an Accept header which + indicates support for both the old MimeTypes + (e.g. application/xml+fhir]]>) + and the new MimeTypes + (e.g. application/fhir+xml]]>) +