Fix #124 - Resource references shouldn't include version when they are
encoded
This commit is contained in:
parent
ef88eb9a97
commit
c8a70c1904
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
|
||||
* </p>
|
||||
|
@ -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
|
||||
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> package instead.
|
||||
*/
|
||||
<T extends IBaseResource> Bundle parseBundle(Class<T> 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
|
||||
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> 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
|
||||
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> 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 <code>this</code> parser so that method calls can be conveniently chained
|
||||
* @return Returns an instance of <code>this</code> 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 <code>this</code> parser so that method calls can be chained together
|
||||
*/
|
||||
IParser setServerBaseUrl(String theUrl);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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("<hr/>");
|
||||
}
|
||||
} 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();
|
||||
|
|
|
@ -1122,7 +1122,6 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
|
|||
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
|
||||
Root<ResourceTable> from = cq.from(ResourceTable.class);
|
||||
// cq.where(builder.equal(from.get("myResourceType"), getContext().getResourceDefinition(myResourceType).getName()));
|
||||
cq.where(from.get("myId").in(theIncludePids));
|
||||
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Patient> findPatient(@RequiredParam(name = "_id") StringParam theParam) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -4,17 +4,22 @@
|
|||
<properties>
|
||||
<author>James Agnew</author>
|
||||
<title>HAPI FHIR Changelog</title>
|
||||
<!--
|
||||
com.phloc:phloc-commons ............................... 4.3.5 -> 4.3.6
|
||||
[INFO] org.apache.httpcomponents:httpclient .................... 4.3.6 -> 4.4
|
||||
[INFO] org.codehaus.woodstox:woodstox-core-asl ............... 4.4.0 -> 4.4.1
|
||||
[INFO] org.slf4j:jcl-over-slf4j ............................. 1.7.9 -> 1.7.10
|
||||
[INFO] org.slf4j:slf4j-api .................................. 1.7.9 -> 1.7.10
|
||||
[INFO] org.springframework:spring-beans ...... 4.1.3.RELEASE -> 4.1.5.RELEASE
|
||||
-->
|
||||
</properties>
|
||||
<body>
|
||||
<release version="1.0" date="TBA">
|
||||
<action type="add">
|
||||
Bump the version of a few dependencies to the
|
||||
latest versions:
|
||||
<![CDATA[
|
||||
<ul>
|
||||
<li>Phloc-commons (for schematron validation) 4.3.5 -> 4.3.6</li>
|
||||
<li>Apache HttpClient 4.3.6 -> 4.4</li>
|
||||
<li>Woodstox 4.4.0 -> 4.4.1</li>
|
||||
<li>SLF4j 1.7.9 -> 1.7.10</li>
|
||||
<li>Spring (used in hapi-fhir-jpaserver-base module) 4.1.3.RELEASE -> 4.1.5.RELEASE</li>
|
||||
</ul>
|
||||
]]>
|
||||
</action>
|
||||
<action type="add">
|
||||
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]
|
||||
</action>
|
||||
<action type="add" fix="124">
|
||||
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.
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.9" date="2015-Mar-14">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue