diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitive.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitive.java index 14b475c1764..06be28d286f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitive.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitive.java @@ -29,7 +29,7 @@ public abstract class BasePrimitive extends BaseIdentifiableElement implement private T myCoercedValue; private String myStringValue; - + @Override public boolean equals(Object theObj) { if (theObj == null) { @@ -72,6 +72,7 @@ public abstract class BasePrimitive extends BaseIdentifiableElement implement if (theValue == null) { myStringValue = null; } else { + // NB this might be null myStringValue = encode(theValue); } } @@ -82,23 +83,26 @@ public abstract class BasePrimitive extends BaseIdentifiableElement implement if (theValue == null) { myCoercedValue = null; } else { + // NB this might be null myCoercedValue = parse(theValue); } } /** - * Subclasses must override to convert an encoded representation of - * this datatype into a "coerced" one + * Subclasses must override to convert an encoded representation of this datatype into a "coerced" one * - * @param theValue Will not be null + * @param theValue + * Will not be null + * @return May return null if the value does not correspond to anything */ protected abstract T parse(String theValue); /** - * Subclasses must override to convert a "coerced" value into an - * encoded one. + * Subclasses must override to convert a "coerced" value into an encoded one. * - * @param theValue Will not be null + * @param theValue + * Will not be null + * @return May return null if the value does not correspond to anything */ protected abstract String encode(T theValue); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/UriDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/UriDt.java index 394c4c4a230..01da301d301 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/UriDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/UriDt.java @@ -33,7 +33,23 @@ import ca.uhn.fhir.parser.DataFormatException; @DatatypeDef(name = "uri") public class UriDt extends BasePrimitive { - private URI myValue; + /** + * Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string + * in the value of the newly created UriDt, per the FHIR specification). + * + * @param theOid + * The OID to use (null is acceptable and will result in a UriDt instance with a + * null value) + * @return A new UriDt instance + */ + public static UriDt fromOid(String theOid) { + if (theOid == null) { + return new UriDt(); + } + return new UriDt("urn:oid:" + theOid); + } + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UriDt.class); /** * Create a new String @@ -51,57 +67,8 @@ public class UriDt extends BasePrimitive { } @Override - public URI getValue() { - return myValue; - } - - @Override - public void setValue(URI theValue) { - myValue = theValue; - } - - @Override - public void setValueAsString(String theValue) throws DataFormatException { - if (theValue == null) { - myValue = null; - } else { - try { - myValue = new URI(theValue); - } catch (URISyntaxException e) { - throw new DataFormatException("Unable to parse URI value", e); - } - } - } - - @Override - public String getValueAsString() { - if (myValue == null) { - return null; - } else { - return myValue.toASCIIString(); - } - } - - @Override - public String toString() { - return getValueAsString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((myValue == null) ? 0 : myValue.hashCode()); - return result; - } - - /** - * Compares the given string to the string representation of this URI. In many cases it is preferable to use this - * instead of the standard {@link #equals(Object)} method, since that method returns false unless it is - * passed an instance of {@link UriDt} - */ - public boolean equals(String theString) { - return StringUtils.equals(getValueAsString(), theString); + protected String encode(URI theValue) { + return getValue().toASCIIString(); } @Override @@ -114,21 +81,42 @@ public class UriDt extends BasePrimitive { return false; UriDt other = (UriDt) obj; - if (myValue == null && other.myValue == null) { + if (getValue() == null && other.getValue() == null) { return true; } - if (myValue == null || other.myValue == null) { + if (getValue() == null || other.getValue() == null) { return false; } - URI normalize = normalize(myValue); - URI normalize2 = normalize(other.myValue); + URI normalize = normalize(getValue()); + URI normalize2 = normalize(other.getValue()); return normalize.equals(normalize2); } - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UriDt.class); + /** + * Compares the given string to the string representation of this URI. In many cases it is preferable to use this + * instead of the standard {@link #equals(Object)} method, since that method returns false unless it is + * passed an instance of {@link UriDt} + */ + public boolean equals(String theString) { + return StringUtils.equals(getValueAsString(), theString); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + URI normalize = normalize(getValue()); + result = prime * result + ((normalize == null) ? 0 : normalize.hashCode()); + + return result; + } private URI normalize(URI theValue) { + if (theValue == null) { + return null; + } URI retVal = (theValue.normalize()); String urlString = retVal.toString(); if (urlString.endsWith("/") && urlString.length() > 1) { @@ -141,20 +129,13 @@ public class UriDt extends BasePrimitive { return retVal; } - /** - * Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string - * in the value of the newly created UriDt, per the FHIR specification). - * - * @param theOid - * The OID to use (null is acceptable and will result in a UriDt instance with a - * null value) - * @return A new UriDt instance - */ - public static UriDt fromOid(String theOid) { - if (theOid == null) { - return new UriDt(); + @Override + protected URI parse(String theValue) { + try { + return new URI(theValue); + } catch (URISyntaxException e) { + throw new DataFormatException("Unable to parse URI value", e); } - return new UriDt("urn:oid:" + theOid); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java index f6eec435eec..2eb81bf9729 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/XhtmlDt.java @@ -41,8 +41,6 @@ import ca.uhn.fhir.util.XmlUtil; @DatatypeDef(name = "xhtml") public class XhtmlDt extends BasePrimitive> { - private List myValue; - /** * Constructor */ @@ -73,18 +71,23 @@ public class XhtmlDt extends BasePrimitive> { */ @Override public void setValueAsString(String theValue) throws DataFormatException { - if (theValue == null) { - myValue = null; - return; - } + // This method is only here for providing specialized javadocs + super.setValueAsString(theValue); + } + + public boolean hasContent() { + return getValue() != null && getValue().size() > 0; + } + + @Override + protected List parse(String theValue) { String val = theValue.trim(); if (!val.startsWith("<")) { val = "
" + val + "
"; } if (val.startsWith("")) { - myValue = null; - return; + return null; } try { @@ -103,7 +106,7 @@ public class XhtmlDt extends BasePrimitive> { value.add(next); } } - setValue(value); + return value; } catch (XMLStreamException e) { throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e); @@ -113,14 +116,11 @@ public class XhtmlDt extends BasePrimitive> { } @Override - public String getValueAsString() throws DataFormatException { - if (myValue == null) { - return null; - } + protected String encode(List theValue) { try { StringWriter w = new StringWriter(); XMLEventWriter ew = XmlUtil.createXmlWriter(w); - for (XMLEvent next : myValue) { + for (XMLEvent next : getValue()) { if (next.isCharacters()) { ew.add(next); } else { @@ -136,18 +136,4 @@ public class XhtmlDt extends BasePrimitive> { } } - @Override - public List getValue() { - return myValue; - } - - @Override - public void setValue(List theValue) throws DataFormatException { - myValue = theValue; - } - - public boolean hasContent() { - return myValue != null && myValue.size() > 0; - } - } diff --git a/hapi-fhir-structures-dstu/.settings/org.eclipse.wst.common.project.facet.core.xml b/hapi-fhir-structures-dstu/.settings/org.eclipse.wst.common.project.facet.core.xml index 5c9bd7532ab..4f92af543f1 100644 --- a/hapi-fhir-structures-dstu/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/hapi-fhir-structures-dstu/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -1,5 +1,5 @@ - + diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java index 37c4b5f037b..8df69dec15a 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java @@ -37,9 +37,10 @@ public class BaseDateTimeDtTest { @Test() public void testParseMalformatted() throws DataFormatException { DateTimeDt dt = new DateTimeDt("20120102"); - assertEquals("2012-01-02",dt.getValueAsString()); - } - + assertEquals("20120102", dt.getValueAsString()); + assertEquals("2012-01-02", new SimpleDateFormat("yyyy-MM-dd").format(dt.getValue())); + } + @Test public void testParseMonth() throws DataFormatException { DateTimeDt dt = new DateTimeDt(); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/XhtmlDtTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/XhtmlDtTest.java index 6be6529d169..40747aa75e7 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/XhtmlDtTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/XhtmlDtTest.java @@ -50,6 +50,15 @@ public class XhtmlDtTest { assertEquals("
amp &
", x.getValueAsString()); } + @Test + public void testOnlyProcessingDirective() { + XhtmlDt x = new XhtmlDt(); + x.setValueAsString(""); + assertEquals(null, x.getValue()); + assertEquals("", x.getValueAsString()); + } + + @Test public void testCharacterEntities() { diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java index 3594ada3176..e3fc9a5a749 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java @@ -5,10 +5,14 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum; +import ca.uhn.fhir.model.primitive.DateTimeDt; + import org.apache.commons.io.IOUtils; +import org.hamcrest.core.StringContains; import org.junit.Test; import java.io.IOException; +import java.text.SimpleDateFormat; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; @@ -31,7 +35,7 @@ public class ResourceValidatorTest { FhirValidator val = ourCtx.newValidator(); val.setValidateAgainstStandardSchema(true); val.setValidateAgainstStandardSchematron(false); - + val.validate(p); p.getAnimal().getBreed().setText("The Breed"); @@ -45,18 +49,43 @@ public class ResourceValidatorTest { } } + /** + * See issue #50 + */ + @Test + public void testOutOfBoundsDate() { + Patient p = new Patient(); + p.setBirthDate(new DateTimeDt("2000-15-31")); + + String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p); + ourLog.info(encoded); + + assertThat(encoded, StringContains.containsString("2000-15-31")); + + p = ourCtx.newXmlParser().parseResource(Patient.class, encoded); + assertEquals("2000-15-31", p.getBirthDate().getValueAsString()); + assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate().getValue())); + + ValidationResult result = ourCtx.newValidator().validateWithResult(p); + String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.getOperationOutcome()); + ourLog.info(resultString); + + assertEquals(2, result.getOperationOutcome().getIssue().size()); + assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3")); + } + @Test public void testSchemaBundleValidator() throws IOException { String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); Bundle b = ourCtx.newXmlParser().parseBundle(res); - FhirValidator val = createFhirValidator(); + FhirValidator val = createFhirValidator(); val.validate(b); Patient p = (Patient) b.getEntries().get(0).getResource(); p.getTelecomFirstRep().setValue("123-4567"); - + try { val.validate(b); fail(); @@ -76,60 +105,60 @@ public class ResourceValidatorTest { val.setValidateAgainstStandardSchema(false); val.setValidateAgainstStandardSchematron(true); - ValidationResult validationResult = val.validateWithResult(p); - assertTrue(validationResult.isSuccessful()); + ValidationResult validationResult = val.validateWithResult(p); + assertTrue(validationResult.isSuccessful()); - p.getTelecomFirstRep().setValue("123-4567"); - validationResult = val.validateWithResult(p); - assertFalse(validationResult.isSuccessful()); - OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome(); - ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome)); - assertEquals(1, operationOutcome.getIssue().size()); - assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided.")); + p.getTelecomFirstRep().setValue("123-4567"); + validationResult = val.validateWithResult(p); + assertFalse(validationResult.isSuccessful()); + OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome(); + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome)); + assertEquals(1, operationOutcome.getIssue().size()); + assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided.")); p.getTelecomFirstRep().setSystem(ContactSystemEnum.EMAIL); - validationResult = val.validateWithResult(p); - assertTrue(validationResult.isSuccessful()); + validationResult = val.validateWithResult(p); + assertTrue(validationResult.isSuccessful()); } - @Test - public void testSchemaBundleValidatorIsSuccessful() throws IOException { - String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); - Bundle b = ourCtx.newXmlParser().parseBundle(res); + @Test + public void testSchemaBundleValidatorIsSuccessful() throws IOException { + String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); + Bundle b = ourCtx.newXmlParser().parseBundle(res); - FhirValidator val = createFhirValidator(); + FhirValidator val = createFhirValidator(); - ValidationResult result = val.validateWithResult(b); - assertTrue(result.isSuccessful()); - OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome(); - assertNotNull(operationOutcome); - assertEquals(0, operationOutcome.getIssue().size()); - } + ValidationResult result = val.validateWithResult(b); + assertTrue(result.isSuccessful()); + OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome(); + assertNotNull(operationOutcome); + assertEquals(0, operationOutcome.getIssue().size()); + } - @Test - public void testSchemaBundleValidatorFails() throws IOException { - String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); - Bundle b = ourCtx.newXmlParser().parseBundle(res); + @Test + public void testSchemaBundleValidatorFails() throws IOException { + String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); + Bundle b = ourCtx.newXmlParser().parseBundle(res); - FhirValidator val = createFhirValidator(); + FhirValidator val = createFhirValidator(); - ValidationResult validationResult = val.validateWithResult(b); - assertTrue(validationResult.isSuccessful()); + ValidationResult validationResult = val.validateWithResult(b); + assertTrue(validationResult.isSuccessful()); - Patient p = (Patient) b.getEntries().get(0).getResource(); - p.getTelecomFirstRep().setValue("123-4567"); - validationResult = val.validateWithResult(b); - assertFalse(validationResult.isSuccessful()); - OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome(); - ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome)); - assertEquals(1, operationOutcome.getIssue().size()); - assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided.")); - } + Patient p = (Patient) b.getEntries().get(0).getResource(); + p.getTelecomFirstRep().setValue("123-4567"); + validationResult = val.validateWithResult(b); + assertFalse(validationResult.isSuccessful()); + OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome(); + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome)); + assertEquals(1, operationOutcome.getIssue().size()); + assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided.")); + } - private FhirValidator createFhirValidator() { - FhirValidator val = ourCtx.newValidator(); - val.setValidateAgainstStandardSchema(true); - val.setValidateAgainstStandardSchematron(true); - return val; - } + private FhirValidator createFhirValidator() { + FhirValidator val = ourCtx.newValidator(); + val.setValidateAgainstStandardSchema(true); + val.setValidateAgainstStandardSchematron(true); + return val; + } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f506487002f..97e9619a513 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -92,6 +92,16 @@ but the version bundled with IBM's JDK is flaky and resulted in a number of problems when deploying to Websphere. + + Primitive datatypes now preserve their original string value when parsing resources, + as well as containing the "parsed value". For instance, a DecimalDt field value of + 1.0000]]> will be parsed into the corresponding + decimal value, but will also retain the original value with the corresponding + level of precision. This allows vadliator rules to be applied to + original values as received "over the wire", such as well formatted but + invalid dates, e.g. "2001-15-01". Thanks to Joe Athman for reporting and + helping to come up with a fix! + diff --git a/src/changes/changes.xml.orig b/src/changes/changes.xml.orig deleted file mode 100644 index f17e6cf22c7..00000000000 --- a/src/changes/changes.xml.orig +++ /dev/null @@ -1,476 +0,0 @@ - - - - James Agnew - HAPI FHIR Changelog - - - -<<<<<<< HEAD:src/changes/changes.xml - - Profile generation on the server was not working due to IdDt being - incorrectly used. Thanks to Bill de Beaubien for the pull request! - - -======= - - API CHANGE:]]> The "FHIR structures" for DSTU1 (the classes which model the - resources and composite datatypes) have been moved out of the core JAR into their - own JAR, in order to allow support for DEV resources, and DSTU2 resources when thast - version is finalized. See - upgrading]]> - for more information. - - - Profile generation on the server was not working due to IdDt being - incorrectly used. Thanks to Bill de Beaubien for the pull request! - - ->>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:src/changes/changes.xml - Profiles did not generate correctly if a resource definition class had a - defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request! - - - Remove unnessecary IOException from narrative generator API. Thanks to - Petro Mykhailysyn for the pull request! - -<<<<<<< HEAD:src/changes/changes.xml -======= - - Introduced a new - @ProvidesResources]]> annotation which can be added to - resource provider and servers to allow them to declare additional resource - classes they are able to serve. This is useful if you have a server which can - serve up multiple classes for the same resource type (e.g. a server that sometimes - returns a default Patient, but sometimes uses a custom subclass). - Thanks to Bill de Beaubien for the pull request! - - - Add a new method to the server interceptor - framework which allows interceptors to be notified of any exceptions and - runtime errors within server methods. Interceptors may optionally also - override the default error handling behaviour of the RestfulServer. - - - Add constants to BaseResource for the "_id" search parameter which all resources - should support. - ->>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:src/changes/changes.xml - - - - API CHANGE:]]> The TagList class previously implemented ArrayList semantics, - but this has been replaced with LinkedHashMap semantics. This means that the list of - tags will no longer accept duplicate tags, but that tag order will still be - preserved. Thanks to Bill de Beaubien for reporting! - - - Server was incorrectly including contained resources being returned as both contained resources, and as - top-level resources in the returned bundle for search operations. - Thanks to Bill de Beaubien for reporting! This also fixes Issue #20, thanks to - lephty for reporting! - - - Documentation fixes - - - Add a collection of new methods on the generic client which support the - read, - read, - and search - ]]> - operations using an absolute URL. This allows developers to perform these operations using - URLs they obtained from other sources (or external resource references within resources). In - addition, the existing read/vread operations will now access absolute URL references if - they are passed in. Thanks to Doug Martin of the Regenstrief Center for Biomedical Informatics - for contributing this implementation! - - - Server implementation was not correctly figuring out its own FHIR Base URL when deployed - on Amazon Web Service server. Thanks to Jeffrey Ting and Bill De Beaubien of - Systems Made Simple for their help in figuring out this issue! - - - XML Parser failed to encode fields with both a resource reference child and - a primitive type child. Thanks to Jeffrey Ting and Bill De Beaubien of - Systems Made Simple for their help in figuring out this issue! - - - HAPI now runs successfully on Servlet 2.5 containers (such as Tomcat 6). Thanks to - Bernard Gitaadji for reporting and diagnosing the issue! - - - Summary (in the bundle entry) is now encoded by the XML and JSON parsers if supplied. Thanks to David Hay of - Orion Health for reporting this! - - - Conformance profiles which are automatically generated by the server were missing a few mandatory elements, - which meant that the profile did not correctly validate. Thanks to Bill de Beaubien of Systems Made Simple - for reporting this! - - - XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those characters - escaped properly in encoded messages. - - - Resources containing entities which are not valid in basic XML (e.g. &sect;) will have those - entities converted to their equivalent unicode characters when resources are encoded, since FHIR does - not allow extended entities in resource instances. - - - Add a new client interceptor which adds HTTP Authorization Bearer Tokens (for use with OAUTH2 servers) - to client requests. - - - Add phloc-commons dependency explicitly, which resolves an issue building HAPI from source on - some platforms. Thanks to Odysseas Pentakalos for the patch! - - - HAPI now logs a single line indicating the StAX implementation being used upon the - first time an XML parser is created. - - - Update methods on the server did not return a "content-location" header, but - only a "location" header. Both are required according to the FHIR specification. - Thanks to Bill de Beaubien of Systems Made Simple for reporting this! - - - Parser failed to correctly read contained Binary resources. Thanks to Alexander Kley for - the patch! - - - Calling encode multiple times on a resource with contained resources caused the contained - resources to be re-added (and the actual message to grow) with each encode pass. Thanks to - Alexander Kley for the test case! - - - JSON-encoded contained resources with the incorrect "_id" element (which should be "id", but some - incorrect examples exist on the FHIR specification) now parse correctly. In other words, HAPI - previously only accepted the correct "id" element, but now it also accepts the incorrect - "_id" element just to be more lenient. - - - Several unit tests failed on Windows (or any platform with non UTF-8 default encoding). This may - have also caused resource validation to fail occasionally on these platforms as well. - Thanks to Bill de Beaubien for reporting! - - - toString() method on TokenParam was incorrectly showing the system as the value. - Thanks to Bill de Beaubien for reporting! - - - Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks - to David Hay of Orion Health for reporting! - - - Add a - Vagrant]]> - based environment (basically a fully built, self contained development environment) for - trying out the HAPI server modules. Thanks to Preston Lee for the pull request, and for - offering to maintain this! - - - Change validation API so that it uses a return type instead of exceptions to communicate - validation failures. Thanks to Joe Athman for the pull request! - - - Add a client interceptor which adds an HTTP cookie to each client request. Thanks to - Petro Mykhailysyn for the pull request! - - - - - - Add server interceptor framework, and new interceptor for logging incoming - requests. - - - Add server validation framework for validating resources against the FHIR schemas and schematrons - - - Tester UI created double _format and _pretty param entries in searches. Thanks to Gered King of University - Health Network for reporting! - - - Create method was incorrectly returning an HTTP 204 on sucessful completion, but - should be returning an HTTP 200 per the FHIR specification. Thanks to wanghaisheng - for reporting! - - - FHIR Tester UI now correctly sends UTF-8 charset in responses so that message payloads containing - non US-ASCII characters will correctly display in the browser - - - JSON parser was incorrectly encoding extensions on composite elements outside the element itself - (as is done correctly for non-composite elements) instead of inside of them. Thanks to David Hay of - Orion for reporting this! - - - Contained/included resource instances received by a client are now automatically - added to any ResourceReferenceDt instancea in other resources which reference them. - - - Add documentation on how to use eBay CORS Filter to support Cross Origin Resource - Sharing (CORS) to server. CORS support that was built in to the server itself has - been removed, as it did not work correctly (and was reinventing a wheel that others - have done a great job inventing). Thanks to Peter Bernhardt of Relay Health for all the assistance - in testing this! - - - IResource interface did not expose the getLanguage/setLanguage methods from BaseResource, - so the resource language was difficult to access. - - - JSON Parser now gives a more friendly error message if it tries to parse JSON with invalid use - of single quotes - - - Transaction server method is now allowed to return an OperationOutcome in addition to the - incoming resources. The public test server now does this in order to return status information - about the transaction processing. - - - Update method in the server can now flag (via a field on the MethodOutcome object being returned) - that the result was actually a creation, and Create method can indicate that it was actually an - update. This has no effect other than to switch between the HTTP 200 and HTTP 201 status codes on the - response, but this may be useful in some circumstances. - - - Annotation client search methods with a specific resource type (e.g. List<Patient> search()) - won't return any resources that aren't of the correct type that are received in a response - bundle (generally these are referenced resources, so they are populated in the reference fields instead). - Thanks to Tahura Chaudhry of University Health Network for the unit test! - - - Added narrative generator template for OperationOutcome resource - - - Date/time types did not correctly parse values in the format "yyyymmdd" (although the FHIR-defined format - is "yyyy-mm-dd" anyhow, and this is correctly handled). Thanks to Jeffrey Ting of Systems Made Simple - for reporting! - - - Server search method for an unnamed query gets called if the client requests a named query - with the same parameter list. Thanks to Neal Acharya of University Health Network for reporting! - - - Category header (for tags) is correctly read in client for "read" operation - - - Transaction method in server can now have parameter type Bundle instead of - List<IResource> - - - HAPI parsers now use field access to get/set values instead of method accessors and mutators. - This should give a small performance boost. - - - JSON parser encodes resource references incorrectly, using the name "resource" instead - of the name "reference" for the actual reference. Thanks to - Ricky Nguyen for reporting and tracking down the issue! - - - Rename NotImpementedException to NotImplementedException (to correct typo) - - - Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with 4xx/5xx status) - - - Fix performance issue in date/time datatypes where pattern matchers were not static - - - Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, but - previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting! - - - Resource of type "List" failed to parse from a bundle correctly. Thanks to David Hay of Orion Health - for reporting! - - - QuantityParam correctly encodes approximate (~) prefix to values - - - If a server defines a method with parameter "_id", incoming search requests for that method may - get delegated to the wrong method. Thanks to Neal Acharya for reporting! - - - SecurityEvent.Object structural element has been renamed to - SecurityEvent.ObjectElement to avoid conflicting names with the - java Object class. Thanks to Laurie Macdougall-Sookraj of UHN for - reporting! - - - Text/narrative blocks that were created with a non-empty - namespace prefix (e.g. <xhtml:div xmlns:xhtml="...">...</xhtml:div>) - failed to encode correctly (prefix was missing in encoded resource) - - - Resource references previously encoded their children (display and reference) - in the wrong order so references with both would fail schema validation. - - - SecurityEvent resource's enums now use friendly enum names instead of the unfriendly - numeric code values. Thanks to Laurie MacDougall-Sookraj of UHN for the - suggestion! - - - - - HAPI has a number of RESTful method parameter types that have similar but not identical - purposes and confusing names. A cleanup has been undertaken to clean this up. - This means that a number of existing classes - have been deprocated in favour of new naming schemes. -
]]> - All annotation-based clients and all server search method parameters are now named - (type)Param, for example: StringParam, TokenParam, etc. -
]]> - All generic/fluent client method parameters are now named - (type)ClientParam, for example: StringClientParam, TokenClientParam, etc. -
]]> - All renamed classes have been retained and deprocated, so this change should not cause any issues - for existing applications but those applications should be refactored to use the - new parameters when possible. -
- - Allow server methods to return wildcard generic types (e.g. List<? extends IResource>) - - - Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as - "&identifier=system|codepart1\|codepart2" - - - Add support for OPTIONS verb (which returns the server conformance statement) - - - Add support for CORS headers in server - - - Bump SLF4j dependency to latest version (1.7.7) - - - Add interceptor framework for clients (annotation based and generic), and add interceptors - for configurable logging, capturing requests and responses, and HTTP basic auth. - - - Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead - of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one! - - - Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion! - - - If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or - an OperationOutcome resource, include the message in the exception message so that it will be - more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion! - - - Read invocations in the client now process the "Content-Location" header and use it to - populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion! - - - Fix issue where vread invocations on server incorrectly get routed to instance history method if one is - defined. Thanks to Neal Acharya from UHN for surfacing this one! - - - Binary reads on a server not include the Content-Disposition header, to prevent HTML in binary - blobs from being used for nefarious purposes. See - FHIR Tracker Bug 3298]]> - for more information. - - - Support has been added for using an HTTP proxy for outgoing requests. - - - Fix: Primitive extensions declared against custom resource types - are encoded even if they have no value. Thanks to David Hay of Orion for - reporting this! - - - Fix: RESTful server deployed to a location where the URL to access it contained a - space (e.g. a WAR file with a space in the name) failed to work correctly. - Thanks to David Hay of Orion for reporting this! - -
- - - BREAKING CHANGE:]]>: IdDt has been modified so that it - contains a partial or complete resource identity. Previously it contained - only the simple alphanumeric id of the resource (the part at the end of the "read" URL for - that resource) but it can now contain a complete URL or even a partial URL (e.g. "Patient/123") - and can optionally contain a version (e.g. "Patient/123/_history/456"). New methods have - been added to this datatype which provide just the numeric portion. See the JavaDoc - for more information. - - - API CHANGE:]]>: Most elements in the HAPI FHIR model contain - a getId() and setId() method. This method is confusing because it is only actually used - for IDREF elements (which are rare) but its name makes it easy to confuse with more - important identifiers. For this reason, these methods have been deprocated and replaced with - get/setElementSpecificId() methods. The old methods will be removed at some point. Resource - types are unchanged and retain their get/setId methods. - - - Allow use of QuantityDt as a service parameter to support the "quantity" type. Previously - QuantityDt did not implement IQueryParameterType so it was not valid, and there was no way to - support quantity search parameters on the server (e.g. Observation.value-quantity) - - - Introduce StringParameter type which can be used as a RESTful operation search parameter - type. StringParameter allows ":exact" matches to be specified in clients, and handled in servers. - - - Parsers (XML/JSON) now support deleted entries in bundles - - - Transaction method now supported in servers - - - Support for Binary resources added (in servers, clients, parsers, etc.) - - - Support for Query resources fixed (in parser) - - - Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a resource) - now parse and encode correctly, meaning that all contained resources are placed in the "contained" element - of the root resource, and the parser looks in the root resource for all container levels when stitching - contained resources back together. - - - Server methods with @Include parameter would sometimes fail when no _include was actually - specified in query strings. - - - Client requests for IdentifierDt types (such as Patient.identifier) did not create the correct - query string if the system is null. - - - Add support for paging responses from RESTful servers. - - - Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are - produced by the Health Intersections server) - - - Server now automatically compresses responses if the client indicates support - - - Server failed to support optional parameters when type is String and :exact qualifier is used - - - Read method in client correctly populated resource ID in returned object - - - Support added for deleted-entry by/name, by/email, and comment from Tombstones spec - - - - - -