Fix #124 - Resource references shouldn't include version when they are

encoded
This commit is contained in:
James Agnew 2015-03-23 07:55:32 +01:00
parent ef88eb9a97
commit c8a70c1904
11 changed files with 262 additions and 21 deletions

View File

@ -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) {

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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";

View File

@ -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() {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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">