diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
index 7358b5556ad..2159f48d9ac 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.parser;
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.Reader;
@@ -65,6 +66,7 @@ public abstract class BaseParser implements IParser {
private ContainedResources myContainedResources;
private FhirContext myContext;
private boolean mySuppressNarratives;
+ private String myServerBaseUrl;
public BaseParser(FhirContext theContext) {
myContext = theContext;
@@ -163,8 +165,9 @@ public abstract class BaseParser implements IParser {
}
protected String determineReferenceText(BaseResourceReferenceDt theRef) {
- String reference = theRef.getReference().getValue();
- if (isBlank(reference)) {
+ IdDt ref = theRef.getReference();
+ if (isBlank(ref.getIdPart())) {
+ String reference = ref.getValue();
if (theRef.getResource() != null) {
IdDt containedId = getContainedResources().getResourceId(theRef.getResource());
if (containedId != null && !containedId.isEmpty()) {
@@ -177,8 +180,22 @@ public abstract class BaseParser implements IParser {
reference = theRef.getResource().getId().getValue();
}
}
+ return reference;
+ } else {
+ if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
+ String reference = ref.toUnqualifiedVersionless().getValue();
+ return reference;
+ } else {
+ String reference = ref.toVersionless().getValue();
+ return reference;
+ }
}
- return reference;
+ }
+
+ @Override
+ public IParser setServerBaseUrl(String theUrl) {
+ myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
+ return this;
}
protected String determineResourceBaseUrl(String bundleBaseUrl, BundleEntry theEntry) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
index c074fe0f146..1ca5680bab5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
@@ -32,7 +32,8 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
/**
- *
+ * A parser, which can be used to convert between HAPI FHIR model/structure objects, and
+ * their respective String wire formats, in either XML or JSON.
*
* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread or every message being parsed/encoded.
*
@@ -66,10 +67,25 @@ public interface IParser {
*/
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
+ /**
+ * Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you
+ * should use {@link #parseResource(Class, Reader)} with the Bundle class found in the
+ * ca.uhn.hapi.fhir.model.[version].resource
package instead.
+ */
Bundle parseBundle(Class theResourceType, Reader theReader);
+ /**
+ * Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you
+ * should use {@link #parseResource(Class, Reader)} with the Bundle class found in the
+ * ca.uhn.hapi.fhir.model.[version].resource
package instead.
+ */
Bundle parseBundle(Reader theReader);
+ /**
+ * Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you
+ * should use {@link #parseResource(Class, String)} with the Bundle class found in the
+ * ca.uhn.hapi.fhir.model.[version].resource
package instead.
+ */
Bundle parseBundle(String theMessageString) throws ConfigurationException, DataFormatException;
/**
@@ -143,7 +159,7 @@ public interface IParser {
*
* @param thePrettyPrint
* The flag
- * @return Returns an instance of this
parser so that method calls can be conveniently chained
+ * @return Returns an instance of this
parser so that method calls can be chained together
*/
IParser setPrettyPrint(boolean thePrettyPrint);
@@ -152,4 +168,14 @@ public interface IParser {
*/
IParser setSuppressNarratives(boolean theSuppressNarratives);
+ /**
+ * Sets the server's base URL used by this parser. If a value is set, resource references
+ * will be turned into relative references if they are provided as absolute URLs but
+ * have a base matching the given base.
+ *
+ * @param theUrl The base URL, e.g. "http://example.com/base"
+ * @return Returns an instance of this
parser so that method calls can be chained together
+ */
+ IParser setServerBaseUrl(String theUrl);
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 4c75a96ae85..6a66c5c3e32 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -49,16 +49,13 @@ import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
import javax.json.stream.JsonParsingException;
-import ca.uhn.fhir.model.base.composite.BaseCodingDt;
-import ca.uhn.fhir.model.primitive.*;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.IBase;
import org.hl7.fhir.instance.model.IBaseResource;
import org.hl7.fhir.instance.model.IPrimitiveType;
-import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype;
@@ -91,6 +88,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
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 45090229e7c..37a497e1130 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
@@ -147,7 +147,9 @@ public class RestfulServerUtils {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
writer.append(theResource.getText().getDiv().getValueAsString());
} else {
- getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer);
+ IParser parser = getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
+ parser.setServerBaseUrl(theServerBase);
+ parser.encodeResourceToWriter(theResource, writer);
}
} finally {
writer.close();
@@ -372,8 +374,9 @@ public class RestfulServerUtils {
writer.append("
");
}
} else {
- IParser newParser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
- newParser.encodeBundleToWriter(bundle, writer);
+ IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
+ parser.setServerBaseUrl(theServerBase);
+ parser.encodeBundleToWriter(bundle, writer);
}
} finally {
writer.close();
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java
index 17b72835bd1..c620e81246e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirResourceDao.java
@@ -1122,7 +1122,6 @@ public abstract class BaseFhirResourceDao extends BaseFhirD
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery cq = builder.createQuery(ResourceTable.class);
Root from = cq.from(ResourceTable.class);
-// cq.where(builder.equal(from.get("myResourceType"), getContext().getResourceDefinition(myResourceType).getName()));
cq.where(from.get("myId").in(theIncludePids));
TypedQuery q = myEntityManager.createQuery(cq);
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java
index 0e1f40d3c75..f1503066ac6 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java
@@ -101,6 +101,26 @@ public class JsonParserTest {
}
+ @Test
+ public void testEncodeOmitsVersionAndBase() {
+ Patient p = new Patient();
+ p.getManagingOrganization().setReference("http://example.com/base/Patient/1/_history/2");
+
+ String enc;
+
+ enc = ourCtx.newJsonParser().encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"http://example.com/base/Patient/1\""));
+
+ enc = ourCtx.newJsonParser().setServerBaseUrl("http://example.com/base").encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"Patient/1\""));
+
+ enc = ourCtx.newJsonParser().setServerBaseUrl("http://example.com/base2").encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"http://example.com/base/Patient/1\""));
+ }
+
@Test
public void testDecimalPrecisionPreserved() {
String number = "52.3779939997090374535378485873776474764643249869328698436986235758587";
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
index 9592db55a38..8591b53b8e2 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
@@ -97,6 +97,26 @@ public class XmlParserTest {
}
+ @Test
+ public void testEncodeOmitsVersionAndBase() {
+ Patient p = new Patient();
+ p.getManagingOrganization().setReference("http://example.com/base/Patient/1/_history/2");
+
+ String enc;
+
+ enc = ourCtx.newXmlParser().encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"http://example.com/base/Patient/1\""));
+
+ enc = ourCtx.newXmlParser().setServerBaseUrl("http://example.com/base").encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"Patient/1\""));
+
+ enc = ourCtx.newXmlParser().setServerBaseUrl("http://example.com/base2").encodeResourceToString(p);
+ ourLog.info(enc);
+ assertThat(enc, containsString("\"http://example.com/base/Patient/1\""));
+ }
+
@Test
public void testDuplicateContainedResources() {
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadTest.java
index ccf7117fd52..70b63010e84 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ReadTest.java
@@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
+import java.io.IOException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
@@ -67,6 +68,19 @@ public class ReadTest {
assertThat(responseContent, stringContainsInOrder("1", "\""));
assertThat(responseContent, not(stringContainsInOrder("1", "\"", "1")));
}
+
+ @Test
+ public void testEncodeConvertsReferencesToRelative() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=xml");
+ HttpResponse status = ourClient.execute(httpGet);
+ String responseContent = IOUtils.toString(status.getEntity().getContent());
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ ourLog.info(responseContent);
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ String ref = ourCtx.newXmlParser().parseResource(Patient.class, responseContent).getManagingOrganization().getReference().getValue();
+ assertEquals("Organization/555", ref);
+ }
@Test
public void testReadJson() throws Exception {
@@ -216,6 +230,7 @@ public class ReadTest {
Patient patient = new Patient();
patient.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
patient.setId("Patient/1/_history/1");
+ patient.getManagingOrganization().setReference("http://localhost:" + ourPort + "/Organization/555/_history/666");
return patient;
}
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
index 5a967ce4821..943cc4f7e58 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
@@ -41,6 +41,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
+import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.IGenericClient;
@@ -63,6 +64,20 @@ public class SearchTest {
private static Server ourServer;
+ @Test
+ public void testEncodeConvertsReferencesToRelative() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef");
+ HttpResponse status = ourClient.execute(httpGet);
+ String responseContent = IOUtils.toString(status.getEntity().getContent());
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ ourLog.info(responseContent);
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ Patient patient = (Patient) ourCtx.newXmlParser().parseBundle(responseContent).getEntries().get(0).getResource();
+ String ref = patient.getManagingOrganization().getReference().getValue();
+ assertEquals("Organization/555", ref);
+ }
+
@Test
public void testOmitEmptyOptionalParam() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=");
@@ -312,6 +327,15 @@ public class SearchTest {
retVal.add(patient);
return retVal;
}
+
+ @Search(queryName="searchWithRef")
+ public Patient searchWithRef() {
+ Patient patient = new Patient();
+ patient.setId("Patient/1/_history/1");
+ patient.getManagingOrganization().setReference("http://localhost:" + ourPort + "/Organization/555/_history/666");
+ return patient;
+ }
+
@Search
public List findPatient(@RequiredParam(name = "_id") StringParam theParam) {
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java
new file mode 100644
index 00000000000..3976340b623
--- /dev/null
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java
@@ -0,0 +1,105 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.dstu2.resource.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.util.PortUtil;
+
+/**
+ * Created by dsotnikov on 2/25/2014.
+ */
+public class SearchDstu2Test {
+
+ private static CloseableHttpClient ourClient;
+ private static FhirContext ourCtx = new FhirContext();
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDstu2Test.class);
+ private static int ourPort;
+
+ private static Server ourServer;
+
+ @Test
+ public void testEncodeConvertsReferencesToRelative() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef");
+ HttpResponse status = ourClient.execute(httpGet);
+ String responseContent = IOUtils.toString(status.getEntity().getContent());
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ ourLog.info(responseContent);
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ Patient patient = (Patient) ourCtx.newXmlParser().parseResource(Bundle.class, responseContent).getEntry().get(0).getResource();
+ String ref = patient.getManagingOrganization().getReference().getValue();
+ assertEquals("Organization/555", ref);
+ }
+
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ ourServer.stop();
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ourPort = PortUtil.findFreePort();
+ ourServer = new Server(ourPort);
+
+ DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
+
+ ServletHandler proxyHandler = new ServletHandler();
+ RestfulServer servlet = new RestfulServer();
+ servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
+
+ servlet.setResourceProviders(patientProvider);
+ ServletHolder servletHolder = new ServletHolder(servlet);
+ proxyHandler.addServletWithMapping(servletHolder, "/*");
+ ourServer.setHandler(proxyHandler);
+ ourServer.start();
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setConnectionManager(connectionManager);
+ ourClient = builder.build();
+
+ }
+
+
+ /**
+ * Created by dsotnikov on 2/25/2014.
+ */
+ public static class DummyPatientResourceProvider implements IResourceProvider {
+
+ @Search(queryName="searchWithRef")
+ public Patient searchWithRef() {
+ Patient patient = new Patient();
+ patient.setId("Patient/1/_history/1");
+ patient.getManagingOrganization().setReference("http://localhost:" + ourPort + "/Organization/555/_history/666");
+ return patient;
+ }
+
+ @Override
+ public Class extends IResource> getResourceType() {
+ return Patient.class;
+ }
+
+ }
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 037ca180a19..f78065d0eda 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -4,17 +4,22 @@
James Agnew
HAPI FHIR Changelog
-
+
+ Bump the version of a few dependencies to the
+ latest versions:
+
+ Phloc-commons (for schematron validation) 4.3.5 -> 4.3.6
+ Apache HttpClient 4.3.6 -> 4.4
+ Woodstox 4.4.0 -> 4.4.1
+ SLF4j 1.7.9 -> 1.7.10
+ Spring (used in hapi-fhir-jpaserver-base module) 4.1.3.RELEASE -> 4.1.5.RELEASE
+
+ ]]>
+
Add support for "profile" and "tag" elements in the resource Meta block
when parsing DSTU2 structures.
@@ -50,6 +55,15 @@
the new syntax required in DSTU2: [resource name]:[search param NAME]
insead of the DSTU1 style [resource name].[search param PATH]
+
+ When encoding resources, the parser will now convert any resource
+ references to versionless references automatically (i.e. it will
+ omit the version part automatically if one is present in the reference)
+ since references between resources must be versionless. Additionally,
+ references in server responses will omit the server base URL part of the
+ reference if the base matches the base for the server giving
+ the response.
+