From 8846477b7b276d9788e2106525ebb82181f51bd3 Mon Sep 17 00:00:00 2001
From: Michael Lawley
Date: Wed, 30 Nov 2016 12:03:02 +1000
Subject: [PATCH 1/5] Ensure that older DSTU3 versions (ie Baltimore / May2016
/ 1.4.0) use the legacy mime types
---
.../fhir/rest/server/RestfulServerUtils.java | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
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 bf3b99d2c03..2b86df6f05e 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
@@ -77,6 +77,15 @@ public class RestfulServerUtils {
private static final HashSet TEXT_ENCODE_ELEMENTS = new HashSet(Arrays.asList("Bundle", "*.text", "*.(mandatory)"));
+ private static String actualDstu3FhirVersion = FhirVersionEnum.DSTU3.getFhirVersionString();
+ static {
+ try {
+ Class c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
+ actualDstu3FhirVersion = (String) c.getDeclaredField("VERSION").get(null);
+ } catch (Exception e) {
+ }
+ }
+
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails);
@@ -757,12 +766,16 @@ public class RestfulServerUtils {
myEncoding = theEncoding;
if (theContentType != null) {
if (theContentType.equals(EncodingEnum.JSON_PLAIN_STRING) || theContentType.equals(EncodingEnum.XML_PLAIN_STRING)) {
- myNonLegacy = !theCtx.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3);
+ FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
+ myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU3)
+ || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && !"1.4.0".equals(actualDstu3FhirVersion));
} else {
myNonLegacy = EncodingEnum.isNonLegacy(theContentType);
}
} else {
- if (theCtx.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
+ FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
+ if (ctxtEnum.isOlderThan(FhirVersionEnum.DSTU3)
+ || (ctxtEnum.isEquivalentTo(FhirVersionEnum.DSTU3) && "1.4.0".equals(actualDstu3FhirVersion))) {
myNonLegacy = null;
} else {
myNonLegacy = Boolean.TRUE;
From a9a350754467d5de70c49cde7d2819b09d80af50 Mon Sep 17 00:00:00 2001
From: Michael Lawley
Date: Tue, 6 Dec 2016 10:32:23 +1000
Subject: [PATCH 2/5] #516 Report invalid values for enums through parser error
handler
---
.../main/java/ca/uhn/fhir/parser/ParserState.java | 6 +++++-
.../ca/uhn/fhir/parser/JsonParserDstu3Test.java | 13 +++++++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
index 73b139a2cff..5539ffba97a 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
@@ -2309,7 +2309,11 @@ class ParserState {
if ("".equals(theValue)) {
myErrorHandler.invalidValue(null, theValue, "Attribute values must not be empty (\"\")");
} else {
- myInstance.setValueAsString(theValue);
+ try {
+ myInstance.setValueAsString(theValue);
+ } catch (IllegalArgumentException e) {
+ myErrorHandler.invalidValue(null, theValue, e.getMessage());
+ }
}
} else if ("id".equals(theName)) {
if (myInstance instanceof IIdentifiableElement) {
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
index b70bcf2ce9e..5f3798ef146 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
@@ -49,6 +49,7 @@ import com.google.common.collect.Sets;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
+import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent;
import ca.uhn.fhir.parser.XmlParserDstu3Test.TestPatientFor327;
@@ -1193,6 +1194,18 @@ public class JsonParserDstu3Test {
assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}", str);
}
+ /**
+ * #516
+ */
+ @Test(expected=DataFormatException.class)
+ public void testInvalidEnumValue() {
+ String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"Education Levels
http://csiro.au/ValueSet/education-levelsA selection of Education Levels
\" }, \"experimental\": true, \"date\": \"2016-07-26\" }";
+ IParser parser = ourCtx.newJsonParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+ ValueSet parsed = parser.parseResource(ValueSet.class, res);
+ fail("DataFormat Invalid attribute exception should be thrown");
+ }
+
/**
* #65
*/
From 2f55fdee2e3c8b050c44c3bca809248828171c5a Mon Sep 17 00:00:00 2001
From: James
Date: Sat, 10 Dec 2016 07:38:23 -0500
Subject: [PATCH 3/5] Credit for #525
---
src/changes/changes.xml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 67e90e373a4..359fadd2801 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -93,6 +93,12 @@
to using Thymeleaf 3. Thanks to GitHub user @gsureshkumar
for reporting!
+
+ When parsing invalid enum values in STU3,
+ report errors through the parserErrorHandler,
+ not by throwing an exception. Thanks to
+ Michael Lawley for the pull request!
+
From 1fba4ff265fb65f5912249adcfc61a5aaf8aab1d Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sat, 10 Dec 2016 08:44:00 -0500
Subject: [PATCH 4/5] Test refactor
---
.../main/java/ca/uhn/fhir/util/TestUtil.java | 46 ++++++++++---------
1 file changed, 25 insertions(+), 21 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java
index eab1e317d52..20837333fd7 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java
@@ -79,27 +79,7 @@ public class TestUtil {
}
- /*
- * Set some system properties randomly after each test.. this is kind of hackish,
- * but it helps us make sure we don't have any tests that depend on a particular
- * environment
- */
- Locale[] availableLocales = { Locale.CANADA, Locale.GERMANY, Locale.TAIWAN };
- Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
- ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
- if (Math.random() < 0.5) {
- ourLog.info("Tests are using WINDOWS line endings and ISO-8851-1");
- System.setProperty("file.encoding", "ISO-8859-1");
- System.setProperty("line.separator", "\r\n");
- } else {
- ourLog.info("Tests are using UNIX line endings and UTF-8");
- System.setProperty("file.encoding", "UTF-8");
- System.setProperty("line.separator", "\n");
- }
- String availableTimeZones[] = { "GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30" };
- String timeZone = availableTimeZones[(int) (Math.random() * availableTimeZones.length)];
- TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
- ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
+ randomizeLocale();
/*
* If we're running a CI build, set all loggers to TRACE level to ensure coverage
@@ -116,4 +96,28 @@ public class TestUtil {
}
}
+ /**
+ * Set some system properties randomly after each test.. this is kind of hackish,
+ * but it helps us make sure we don't have any tests that depend on a particular
+ * environment
+ */
+ public static void randomizeLocale() {
+ Locale[] availableLocales = { Locale.CANADA, Locale.GERMANY, Locale.TAIWAN };
+ Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
+ ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
+ if (Math.random() < 0.5) {
+ ourLog.info("Tests are using WINDOWS line endings and ISO-8851-1");
+ System.setProperty("file.encoding", "ISO-8859-1");
+ System.setProperty("line.separator", "\r\n");
+ } else {
+ ourLog.info("Tests are using UNIX line endings and UTF-8");
+ System.setProperty("file.encoding", "UTF-8");
+ System.setProperty("line.separator", "\n");
+ }
+ String availableTimeZones[] = { "GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30" };
+ String timeZone = availableTimeZones[(int) (Math.random() * availableTimeZones.length)];
+ TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+ ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
+ }
+
}
From ee63bbea745ea25a1bf199a4153bb35b3ccf808e Mon Sep 17 00:00:00 2001
From: James
Date: Sat, 10 Dec 2016 14:14:22 -0500
Subject: [PATCH 5/5] Fix #516 - Handle STU3 invalid enum values with an
appropriate exception
---
.../java/example/PagingPatientProvider.java | 95 ++++++-----
.../uhn/fhir/parser/LenientErrorHandler.java | 52 +++++-
.../java/ca/uhn/fhir/parser/ParserState.java | 2 +
.../fhir/validation/ValidationContext.java | 8 +-
.../ResourceProviderDstu3ValueSetTest.java | 31 ++++
.../resources/bug_516_invalid_expansion.json | 47 ++++++
.../src/test/resources/dstu1_bundle.xml | 2 -
.../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 8 +-
.../validation/ResourceValidatorTest.java | 21 ++-
.../uhn/fhir/parser/JsonParserDstu2Test.java | 5 +-
.../uhn/fhir/parser/XmlParserDstu2Test.java | 5 +-
.../ResourceValidatorDstu2Test.java | 31 ++--
.../fhir/dstu3/model/BaseDateTimeType.java | 13 +-
.../hl7/fhir/dstu3/model/PrimitiveType.java | 2 +-
.../ca/uhn/fhir/parser/ErrorHandlerTest.java | 11 +-
.../uhn/fhir/parser/JsonParserDstu3Test.java | 153 +++++++++++++++---
.../uhn/fhir/parser/XmlParserDstu3Test.java | 16 +-
.../ResourceValidatorDstu3Test.java | 47 +++++-
src/changes/changes.xml | 15 ++
src/site/xdoc/doc_validation.xml | 133 +++++++--------
20 files changed, 524 insertions(+), 173 deletions(-)
create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/bug_516_invalid_expansion.json
diff --git a/examples/src/main/java/example/PagingPatientProvider.java b/examples/src/main/java/example/PagingPatientProvider.java
index 8112b4677eb..ab398a3679a 100644
--- a/examples/src/main/java/example/PagingPatientProvider.java
+++ b/examples/src/main/java/example/PagingPatientProvider.java
@@ -13,69 +13,68 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
-
@SuppressWarnings("null")
-//START SNIPPET: provider
+// START SNIPPET: provider
public class PagingPatientProvider implements IResourceProvider {
- /**
- * Search for Patient resources matching a given family name
- */
- @Search
- public IBundleProvider search(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamily) {
- final InstantDt searchTime = InstantDt.withCurrentTime();
+ /**
+ * Search for Patient resources matching a given family name
+ */
+ @Search
+ public IBundleProvider search(@RequiredParam(name = Patient.SP_FAMILY) StringParam theFamily) {
+ final InstantDt searchTime = InstantDt.withCurrentTime();
- /*
- * First, we'll search the database for a set of database row IDs that
- * match the given search criteria. That way we can keep just the
- * row IDs around, and load the actual resources on demand later
- * as the client pages through them.
- */
- final List matchingResourceIds = null; // <-- implement this
+ /**
+ * First, we'll search the database for a set of database row IDs that
+ * match the given search criteria. That way we can keep just the row IDs
+ * around, and load the actual resources on demand later as the client
+ * pages through them.
+ */
+ final List matchingResourceIds = null; // <-- implement this
- /*
- * Return a bundle provider which can page through the IDs and
- * return the resources that go with them.
- */
- return new IBundleProvider() {
+ /**
+ * Return a bundle provider which can page through the IDs and return the
+ * resources that go with them.
+ */
+ return new IBundleProvider() {
- @Override
- public int size() {
- return matchingResourceIds.size();
- }
+ @Override
+ public int size() {
+ return matchingResourceIds.size();
+ }
- @Override
- public List getResources(int theFromIndex, int theToIndex) {
- int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
- List idsToReturn = matchingResourceIds.subList(theFromIndex, end);
- return loadResourcesByIds(idsToReturn);
- }
+ @Override
+ public List getResources(int theFromIndex, int theToIndex) {
+ int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
+ List idsToReturn = matchingResourceIds.subList(theFromIndex, end);
+ return loadResourcesByIds(idsToReturn);
+ }
- @Override
- public InstantDt getPublished() {
- return searchTime;
- }
+ @Override
+ public InstantDt getPublished() {
+ return searchTime;
+ }
@Override
public Integer preferredPageSize() {
// Typically this method just returns null
return null;
}
- };
- }
+ };
+ }
- /**
- * Load a list of patient resources given their IDs
- */
- private List loadResourcesByIds(List theIdsToReturn) {
- // .. implement this search against the database ..
- return null;
- }
+ /**
+ * Load a list of patient resources given their IDs
+ */
+ private List loadResourcesByIds(List theIdsToReturn) {
+ // .. implement this search against the database ..
+ return null;
+ }
- @Override
- public Class extends IResource> getResourceType() {
- return Patient.class;
- }
+ @Override
+ public Class extends IResource> getResourceType() {
+ return Patient.class;
+ }
}
-//END SNIPPET: provider
+// END SNIPPET: provider
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java
index d45af3dbc4b..fced37beb77 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java
@@ -1,5 +1,8 @@
package ca.uhn.fhir.parser;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
@@ -24,7 +27,13 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
*/
/**
- * The default error handler, which logs issues but does not abort parsing
+ * The default error handler, which logs issues but does not abort parsing, with only one exception:
+ *
+ * The {@link #invalidValue(ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation, String, String)}
+ * method will throw a {@link DataFormatException} by default since ignoring this type of error
+ * can lead to data loss (since invalid values are silently ignored). See
+ * {@link #setErrorOnInvalidValue(boolean)} for information on this.
+ *
*
* @see IParser#setParserErrorHandler(IParserErrorHandler)
* @see FhirContext#setParserErrorHandler(IParserErrorHandler)
@@ -32,6 +41,8 @@ import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
public class LenientErrorHandler implements IParserErrorHandler {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
+ private static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler();
+ private boolean myErrorOnInvalidValue = true;
private boolean myLogErrors;
/**
@@ -67,11 +78,30 @@ public class LenientErrorHandler implements IParserErrorHandler {
@Override
public void invalidValue(IParseLocation theLocation, String theValue, String theError) {
- if (myLogErrors) {
- ourLog.warn("Invalid attribute value \"{}\": {}", theValue, theError);
+ if (isBlank(theValue) || myErrorOnInvalidValue == false) {
+ if (myLogErrors) {
+ ourLog.warn("Invalid attribute value \"{}\": {}", theValue, theError);
+ }
+ } else {
+ STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError);
}
}
+ /**
+ * If set to false
(default is true
) invalid values will be logged. By
+ * default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
+ * other methods in this class which default to simply logging errors).
+ *
+ * Note that empty values (e.g. ""
) will not lead to an error when this is set to
+ * true
, only invalid values (e.g. a gender code of foo
)
+ *
+ *
+ * @see #setErrorOnInvalidValue(boolean)
+ */
+ public boolean isErrorOnInvalidValue() {
+ return myErrorOnInvalidValue;
+ }
+
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
@@ -79,6 +109,22 @@ public class LenientErrorHandler implements IParserErrorHandler {
}
}
+ /**
+ * If set to false
(default is true
) invalid values will be logged. By
+ * default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
+ * other methods in this class which default to simply logging errors).
+ *
+ * Note that empty values (e.g. ""
) will not lead to an error when this is set to
+ * true
, only invalid values (e.g. a gender code of foo
)
+ *
+ * @return Returns a reference to this
for easy method chaining
+ * @see #isErrorOnInvalidValue()
+ */
+ public LenientErrorHandler setErrorOnInvalidValue(boolean theErrorOnInvalidValue) {
+ myErrorOnInvalidValue = theErrorOnInvalidValue;
+ return this;
+ }
+
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
index 5539ffba97a..bc3f927af0e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
@@ -2311,6 +2311,8 @@ class ParserState {
} else {
try {
myInstance.setValueAsString(theValue);
+ } catch (DataFormatException e) {
+ myErrorHandler.invalidValue(null, theValue, e.getMessage());
} catch (IllegalArgumentException e) {
myErrorHandler.invalidValue(null, theValue, e.getMessage());
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationContext.java
index fd898b378d3..ef9c9432111 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationContext.java
@@ -25,6 +25,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@@ -146,7 +148,11 @@ public class ValidationContext extends BaseValidationContext implements IV
@Override
public IBaseResource getResource() {
if (myParsed == null) {
- myParsed = getResourceAsStringEncoding().newParser(getFhirContext()).parseResource(getResourceAsString());
+ IParser parser = getResourceAsStringEncoding().newParser(getFhirContext());
+ LenientErrorHandler errorHandler = new LenientErrorHandler();
+ errorHandler.setErrorOnInvalidValue(false);
+ parser.setParserErrorHandler(errorHandler);
+ myParsed = parser.parseResource(getResourceAsString());
}
return myParsed;
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
index 5bd28142308..7f57c782ee2 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java
@@ -11,7 +11,13 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
@@ -34,6 +40,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
+import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
@@ -54,6 +61,30 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
}
+ /**
+ * #516
+ */
+ @Test
+ public void testInvalidFilter() throws Exception {
+ String string = IOUtils.toString(getClass().getResourceAsStream("/bug_516_invalid_expansion.json"), StandardCharsets.UTF_8);
+ HttpPost post = new HttpPost(ourServerBase+"/ValueSet/%24expand");
+ post.setEntity(new StringEntity(string, ContentType.parse(Constants.CT_FHIR_JSON_NEW)));
+
+ CloseableHttpResponse resp = ourHttpClient.execute(post);
+ try {
+
+ String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(respString);
+
+ ourLog.info(resp.toString());
+
+ assertEquals(400, resp.getStatusLine().getStatusCode());
+ assertThat(respString, containsString("Unknown FilterOperator code 'n'"));
+
+ }finally {
+ IOUtils.closeQuietly(resp);
+ }
+ }
private CodeSystem createExternalCs() {
CodeSystem codeSystem = new CodeSystem();
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/bug_516_invalid_expansion.json b/hapi-fhir-jpaserver-base/src/test/resources/bug_516_invalid_expansion.json
new file mode 100644
index 00000000000..93ccab739e6
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/resources/bug_516_invalid_expansion.json
@@ -0,0 +1,47 @@
+{
+ "resourceType": "ValueSet",
+ "url": "http://sample/ValueSet/education-levels",
+ "version": "1",
+ "name": "Education Levels",
+ "status": "draft",
+ "compose": {
+ "include": [
+ {
+ "filter": [
+ {
+ "property": "n",
+ "op": "n",
+ "value": "365460000"
+ }
+ ],
+ "system": "http://snomed.info/sct"
+ }
+ ],
+ "exclude": [
+ {
+ "concept": [
+ {
+ "code": "224298008"
+ },
+ {
+ "code": "365460000"
+ },
+ {
+ "code": "473462005"
+ },
+ {
+ "code": "424587006"
+ }
+ ],
+ "system": "http://snomed.info/sct"
+ }
+ ]
+ },
+ "description": "A\nselection of Education Levels",
+ "text": {
+ "status": "generated",
+ "div": "Education\nLevels
http://csiro.au/ValueSet/education-levelsA selection of\nEducation Levels
"
+ },
+ "experimental": true,
+ "date": "2016-07-26"
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/dstu1_bundle.xml b/hapi-fhir-jpaserver-base/src/test/resources/dstu1_bundle.xml
index 5f9e5fc10fe..79c6b5bdba3 100644
--- a/hapi-fhir-jpaserver-base/src/test/resources/dstu1_bundle.xml
+++ b/hapi-fhir-jpaserver-base/src/test/resources/dstu1_bundle.xml
@@ -1019,14 +1019,12 @@
-
-
diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
index a5d5dda1101..316ab330624 100644
--- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
+++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
@@ -27,6 +27,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
+import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
@@ -49,10 +50,13 @@ public class JpaServerDemo extends RestfulServer {
* We want to support FHIR DSTU2 format. This means that the server
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
*
- * If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
+ * If you want to use DSTU1 instead, change the following line, and
+ * change the 2 occurrences of dstu2 in web.xml to dstu1
*/
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
- setFhirContext(new FhirContext(fhirVersion));
+ FhirContext context = new FhirContext(fhirVersion);
+
+ setFhirContext(context);
// Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
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 b49a9a943a5..364ab4c9ba5 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
@@ -20,6 +20,8 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.util.TestUtil;
public class ResourceValidatorTest {
@@ -72,23 +74,34 @@ public class ResourceValidatorTest {
/**
* See issue #50
*/
- @Test(expected=DataFormatException.class)
+ @Test()
public void testOutOfBoundsDate() {
Patient p = new Patient();
p.setBirthDate(new DateTimeDt("2000-12-31"));
// Put in an invalid date
- String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
+ IParser parser = ourCtx.newXmlParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+
+ String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31"));
- ValidationResult result = ourCtx.newValidator().validateWithResult(encoded);
- String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
+ FhirValidator validator = ourCtx.newValidator();
+ ValidationResult result = validator.validateWithResult(encoded);
+ String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString);
assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3"));
+
+ try {
+ parser.parseResource(encoded);
+ fail();
+ } catch (DataFormatException e) {
+ assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
+ }
}
@Test
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
index 97d3d8b904b..7cd5019b17f 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
@@ -170,7 +170,10 @@ public class JsonParserDstu2Test {
@Test
public void testParseEmptyValue() {
String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}";
- QuestionnaireResponse qr = ourCtx.newJsonParser().parseResource(QuestionnaireResponse.class, input);
+ IParser parser = ourCtx.newJsonParser();
+
+ parser.setParserErrorHandler(new LenientErrorHandler().setErrorOnInvalidValue(false));
+ QuestionnaireResponse qr = parser.parseResource(QuestionnaireResponse.class, input);
assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue());
assertEquals(null, qr.getAuthored());
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
index 4e0cdfb7c30..441acc63334 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
@@ -2524,7 +2524,10 @@ public class XmlParserDstu2Test {
"";
//@formatter:on
- ourCtx.newXmlParser().parseResource(resource);
+
+ IParser parser = ourCtx.newXmlParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+ parser.parseResource(resource);
}
@Test
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/validation/ResourceValidatorDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/validation/ResourceValidatorDstu2Test.java
index bf384296410..415b2eed102 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/validation/ResourceValidatorDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/validation/ResourceValidatorDstu2Test.java
@@ -40,10 +40,12 @@ import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
import ca.uhn.fhir.model.dstu2.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.dstu2.valueset.UnitsOfTimeEnum;
import ca.uhn.fhir.model.primitive.DateDt;
+import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParserDstu2Test.TestPatientFor327;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.schematron.SchematronBaseValidator;
@@ -71,26 +73,33 @@ public class ResourceValidatorDstu2Test {
/**
* See issue #50
*/
- @Test(expected=DataFormatException.class)
+ @Test()
public void testOutOfBoundsDate() {
Patient p = new Patient();
- p.setBirthDate(new DateDt("2000-15-31"));
+ p.setBirthDate(new DateDt("2000-12-31"));
- String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p);
+ // Put in an invalid date
+ IParser parser = ourCtx.newXmlParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+
+ String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31"));
- p = ourCtx.newXmlParser().parseResource(Patient.class, encoded);
- assertEquals("2000-15-31", p.getBirthDateElement().getValueAsString());
- assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate()));
-
- ValidationResult result = ourCtx.newValidator().validateWithResult(p);
- String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
+ ValidationResult result = ourCtx.newValidator().validateWithResult(encoded);
+ String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString);
- assertEquals(2, ((OperationOutcome) result.toOperationOutcome()).getIssue().size());
- assertThat(resultString, StringContains.containsString("2000-15-31"));
+ assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
+ assertThat(resultString, StringContains.containsString("cvc-pattern-valid"));
+
+ try {
+ parser.parseResource(encoded);
+ fail();
+ } catch (DataFormatException e) {
+ assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
+ }
}
@SuppressWarnings("deprecation")
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
index ecc194d87f4..c91d54658f5 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
@@ -1,6 +1,7 @@
package org.hl7.fhir.dstu3.model;
import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.hamcrest.Matchers.emptyOrNullString;
import java.util.Calendar;
import java.util.Date;
@@ -334,7 +335,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
myFractionalSeconds = "";
}
- setPrecision(precision);
+ myPrecision = precision;
return cal.getTime();
}
@@ -372,8 +373,8 @@ public abstract class BaseDateTimeType extends PrimitiveType {
if (isBlank(theValue)) {
throwBadDateFormat(theWholeValue);
} else if (theValue.charAt(0) == 'Z') {
- clearTimeZone();
- setTimeZoneZulu(true);
+ myTimeZone = null;
+ myTimeZoneZulu = true;
} else if (theValue.length() != 6) {
throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\"");
} else if (theValue.charAt(3) != ':' || !(theValue.charAt(0) == '+' || theValue.charAt(0) == '-')) {
@@ -381,8 +382,8 @@ public abstract class BaseDateTimeType extends PrimitiveType {
} else {
parseInt(theWholeValue, theValue.substring(1, 3), 0, 23);
parseInt(theWholeValue, theValue.substring(4, 6), 0, 59);
- clearTimeZone();
- setTimeZone(TimeZone.getTimeZone("GMT" + theValue));
+ myTimeZoneZulu = false;
+ myTimeZone = TimeZone.getTimeZone("GMT" + theValue);
}
return this;
@@ -390,12 +391,14 @@ public abstract class BaseDateTimeType extends PrimitiveType {
public BaseDateTimeType setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone;
+ myTimeZoneZulu = false;
updateStringValue();
return this;
}
public BaseDateTimeType setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu;
+ myTimeZone = null;
updateStringValue();
return this;
}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/PrimitiveType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/PrimitiveType.java
index ec9cd59c732..f9e3d4d6037 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/PrimitiveType.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/PrimitiveType.java
@@ -70,13 +70,13 @@ public abstract class PrimitiveType extends Type implements IPrimitiveType
}
public void fromStringValue(String theValue) {
+ myStringValue = theValue;
if (theValue == null) {
myCoercedValue = null;
} else {
// NB this might be null
myCoercedValue = parse(theValue);
}
- myStringValue = theValue;
}
public T getValue() {
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java
index 42d721ebff6..3e6e4f31137 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.parser;
+import static org.junit.Assert.*;
+
import org.junit.Test;
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
@@ -46,7 +48,14 @@ public class ErrorHandlerTest {
new LenientErrorHandler().containedResourceWithNoId(null);
new LenientErrorHandler().unknownReference(null, null);
new LenientErrorHandler().incorrectJsonType(null, null, ValueType.ARRAY, ValueType.SCALAR);
- new LenientErrorHandler().invalidValue(null, null, null);
+ new LenientErrorHandler().setErrorOnInvalidValue(false).invalidValue(null, "FOO", "");
+ new LenientErrorHandler().invalidValue(null, null, "");
+ try {
+ new LenientErrorHandler().invalidValue(null, "FOO", "");
+ fail();
+ } catch (DataFormatException e) {
+ // good, this one method defaults to causing an error
+ }
}
@Test(expected = DataFormatException.class)
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
index 5f3798ef146..f9072a0788b 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java
@@ -13,35 +13,78 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.ObjectInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
-import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Address.AddressUse;
import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory;
+import org.hl7.fhir.dstu3.model.Attachment;
+import org.hl7.fhir.dstu3.model.AuditEvent;
+import org.hl7.fhir.dstu3.model.Basic;
+import org.hl7.fhir.dstu3.model.Binary;
+import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
+import org.hl7.fhir.dstu3.model.Claim;
+import org.hl7.fhir.dstu3.model.Coding;
+import org.hl7.fhir.dstu3.model.Communication;
+import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
+import org.hl7.fhir.dstu3.model.Conformance;
import org.hl7.fhir.dstu3.model.Conformance.UnknownContentCode;
+import org.hl7.fhir.dstu3.model.Coverage;
+import org.hl7.fhir.dstu3.model.DateTimeType;
+import org.hl7.fhir.dstu3.model.DateType;
+import org.hl7.fhir.dstu3.model.DecimalType;
+import org.hl7.fhir.dstu3.model.DiagnosticReport;
+import org.hl7.fhir.dstu3.model.EnumFactory;
import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
+import org.hl7.fhir.dstu3.model.ExplanationOfBenefit;
+import org.hl7.fhir.dstu3.model.Extension;
+import org.hl7.fhir.dstu3.model.HumanName;
+import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
+import org.hl7.fhir.dstu3.model.Linkage;
+import org.hl7.fhir.dstu3.model.Medication;
+import org.hl7.fhir.dstu3.model.MedicationRequest;
+import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
-import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.dstu3.model.Parameters;
+import org.hl7.fhir.dstu3.model.Patient;
+import org.hl7.fhir.dstu3.model.PrimitiveType;
+import org.hl7.fhir.dstu3.model.Quantity;
+import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
+import org.hl7.fhir.dstu3.model.Reference;
+import org.hl7.fhir.dstu3.model.RelatedPerson;
+import org.hl7.fhir.dstu3.model.SampledData;
+import org.hl7.fhir.dstu3.model.SimpleQuantity;
+import org.hl7.fhir.dstu3.model.StringType;
+import org.hl7.fhir.dstu3.model.UriType;
+import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
-import org.junit.*;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -49,7 +92,6 @@ import com.google.common.collect.Sets;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
-import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
import ca.uhn.fhir.parser.PatientWithExtendedContactDstu3.CustomContactComponent;
import ca.uhn.fhir.parser.XmlParserDstu3Test.TestPatientFor327;
@@ -93,7 +135,7 @@ public class JsonParserDstu3Test {
IParser p = ourCtx.newJsonParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(resource);
ourLog.info(encoded);
-
+
assertEquals(3, countMatches(encoded, "resourceType"));
}
@@ -122,15 +164,18 @@ public class JsonParserDstu3Test {
*/
@Test
public void testParseEmptyValue() {
- String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"item\":[{\"item\":[{\"linkId\":\"\"}]}]}";
- QuestionnaireResponse qr = ourCtx.newJsonParser().parseResource(QuestionnaireResponse.class, input);
-
+ String input = "{\"resourceType\":\"QuestionnaireResponse\",\"id\":\"123\",\"authored\":\"\",\"group\":{\"linkId\":\"\"}}";
+ IParser parser = ourCtx.newJsonParser();
+
+ parser.setParserErrorHandler(new LenientErrorHandler().setErrorOnInvalidValue(false));
+ QuestionnaireResponse qr = parser.parseResource(QuestionnaireResponse.class, input);
+
assertEquals("QuestionnaireResponse/123", qr.getIdElement().getValue());
assertEquals(null, qr.getAuthored());
assertEquals(null, qr.getAuthoredElement().getValue());
assertEquals(null, qr.getAuthoredElement().getValueAsString());
- assertEquals(null, qr.getItemFirstRep().getItemFirstRep().getLinkId());
- assertEquals(null, qr.getItemFirstRep().getItemFirstRep().getLinkIdElement().getValue());
+ assertEquals(null, qr.getItemFirstRep().getLinkId());
+ assertEquals(null, qr.getItemFirstRep().getLinkIdElement().getValue());
}
/**
@@ -1194,17 +1239,77 @@ public class JsonParserDstu3Test {
assertEquals("{\"resourceType\":\"Observation\",\"valueQuantity\":{\"value\":0.0000000000000001}}", str);
}
- /**
- * #516
- */
- @Test(expected=DataFormatException.class)
- public void testInvalidEnumValue() {
- String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"Education Levels
http://csiro.au/ValueSet/education-levelsA selection of Education Levels
\" }, \"experimental\": true, \"date\": \"2016-07-26\" }";
- IParser parser = ourCtx.newJsonParser();
- parser.setParserErrorHandler(new StrictErrorHandler());
- ValueSet parsed = parser.parseResource(ValueSet.class, res);
- fail("DataFormat Invalid attribute exception should be thrown");
- }
+ /**
+ * #516
+ */
+ @Test(expected = DataFormatException.class)
+ public void testInvalidEnumValue() {
+ String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"Education Levels
http://csiro.au/ValueSet/education-levelsA selection of Education Levels
\" }, \"experimental\": true, \"date\": \"2016-07-26\" }";
+ IParser parser = ourCtx.newJsonParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+ ValueSet parsed = parser.parseResource(ValueSet.class, res);
+ fail("DataFormat Invalid attribute exception should be thrown");
+ }
+
+ @Test
+ public void testInvalidEnumValueBlank() {
+ IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
+
+ String res = "{ \"resourceType\": \"Patient\", \"gender\": \"\" }";
+ IParser parser = ourCtx.newJsonParser();
+ parser.setParserErrorHandler(errorHandler);
+ Patient parsed = parser.parseResource(Patient.class, res);
+
+ assertEquals(null, parsed.getGenderElement().getValue());
+ assertEquals(null, parsed.getGenderElement().getValueAsString());
+
+ ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class);
+ verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq(""), msgCaptor.capture());
+ assertEquals("Attribute values must not be empty (\"\")", msgCaptor.getValue());
+
+ String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
+ assertEquals("{\"resourceType\":\"Patient\"}", encoded);
+ }
+
+ @Test
+ public void testInvalidEnumValueInvalid() {
+ IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
+
+ String res = "{ \"resourceType\": \"Patient\", \"gender\": \"foo\" }";
+ IParser parser = ourCtx.newJsonParser();
+ parser.setParserErrorHandler(errorHandler);
+ Patient parsed = parser.parseResource(Patient.class, res);
+
+ assertEquals(null, parsed.getGenderElement().getValue());
+ assertEquals("foo", parsed.getGenderElement().getValueAsString());
+
+ ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class);
+ verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
+ assertEquals("Unknown AdministrativeGender code 'foo'", msgCaptor.getValue());
+
+ String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
+ assertEquals("{\"resourceType\":\"Patient\",\"gender\":\"foo\"}", encoded);
+ }
+
+ @Test
+ public void testInvalidDateTimeValueInvalid() throws Exception {
+ IParserErrorHandler errorHandler = mock(IParserErrorHandler.class);
+
+ String res = "{ \"resourceType\": \"Observation\", \"valueDateTime\": \"foo\" }";
+ IParser parser = ourCtx.newJsonParser();
+ parser.setParserErrorHandler(errorHandler);
+ Observation parsed = parser.parseResource(Observation.class, res);
+
+ assertEquals(null, parsed.getValueDateTimeType().getValue());
+ assertEquals("foo", parsed.getValueDateTimeType().getValueAsString());
+
+ ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class);
+ verify(errorHandler, times(1)).invalidValue(isNull(IParseLocation.class), eq("foo"), msgCaptor.capture());
+ assertEquals("Invalid date/time format: \"foo\"", msgCaptor.getValue());
+
+ String encoded = ourCtx.newJsonParser().encodeResourceToString(parsed);
+ assertEquals("{\"resourceType\":\"Observation\",\"valueDateTime\":\"foo\"}", encoded);
+ }
/**
* #65
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java
index 8150d592e10..3ac7d05a430 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java
@@ -2855,7 +2855,7 @@ public class XmlParserDstu3Test {
/**
* See #366
*/
- @Test(expected = DataFormatException.class)
+ @Test()
public void testParseInvalidBoolean() {
//@formatter:off
String resource = "\n" +
@@ -2863,7 +2863,19 @@ public class XmlParserDstu3Test {
"";
//@formatter:on
- ourCtx.newXmlParser().parseResource(resource);
+ IParser p = ourCtx.newXmlParser();
+
+ try {
+ p.parseResource(resource);
+ fail();
+ } catch (DataFormatException e) {
+ assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"1\": Invalid boolean string: '1'", e.getMessage());
+ }
+
+ LenientErrorHandler errorHandler = new LenientErrorHandler();
+ assertEquals(true, errorHandler.isErrorOnInvalidValue());
+ errorHandler.setErrorOnInvalidValue(false);
+ p.setParserErrorHandler(errorHandler);
}
@Test
diff --git a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java
index 952f3c79771..8dcf1745855 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/validation/ResourceValidatorDstu3Test.java
@@ -3,8 +3,10 @@ package org.hl7.fhir.dstu3.hapi.validation;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
@@ -12,14 +14,24 @@ import java.util.Date;
import java.util.List;
import org.apache.commons.io.IOUtils;
-import org.hl7.fhir.dstu3.model.*;
+import org.hamcrest.core.StringContains;
+import org.hl7.fhir.dstu3.model.CodeableConcept;
+import org.hl7.fhir.dstu3.model.Coding;
+import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
+import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
+import org.hl7.fhir.dstu3.model.OperationOutcome;
+import org.hl7.fhir.dstu3.model.Patient;
+import org.hl7.fhir.dstu3.model.Reference;
+import org.hl7.fhir.dstu3.model.StringType;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParserDstu3Test;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
@@ -37,6 +49,39 @@ public class ResourceValidatorDstu3Test {
TestUtil.clearAllStaticFieldsForUnitTest();
}
+ /**
+ * See issue #50
+ */
+ @Test()
+ public void testOutOfBoundsDate() {
+ Patient p = new Patient();
+ p.setBirthDateElement(new DateType("2000-12-31"));
+
+ // Put in an invalid date
+ IParser parser = ourCtx.newXmlParser();
+ parser.setParserErrorHandler(new StrictErrorHandler());
+
+ String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
+ ourLog.info(encoded);
+
+ assertThat(encoded, StringContains.containsString("2000-15-31"));
+
+ ValidationResult result = ourCtx.newValidator().validateWithResult(encoded);
+ String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
+ ourLog.info(resultString);
+
+ assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
+ assertThat(resultString, StringContains.containsString("cvc-pattern-valid"));
+
+ try {
+ parser.parseResource(encoded);
+ fail();
+ } catch (DataFormatException e) {
+ assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
+ }
+ }
+
+
/**
* Make sure that the elements that appear in all resources (meta, language, extension, etc) all appear in the correct order
*/
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 359fadd2801..9a1ae9fa9f8 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -99,6 +99,21 @@
not by throwing an exception. Thanks to
Michael Lawley for the pull request!
+
+ When parsing DSTU3 resources with enumerated
+ types that contain invalid values, the parser will now
+ invoke the parserErrorHandler. For example, when parsing
+ {"resourceType":"Patient", "gender":"foo"}
+ ]]>
+ the previous behaviour was to throw an InvalidArgumentException.
+ Now, the parserErrorHandler is invoked. In addition, thw
+ LenientErrorHandler has been modified so that this one case
+ will result in a DataFormatException. This has the effect
+ that servers which receive an invalid enum velue will return
+ an HTTP 400 instead of an HTTP 500. Thanks to Jim
+ Steel for reporting!
+
diff --git a/src/site/xdoc/doc_validation.xml b/src/site/xdoc/doc_validation.xml
index 8f7c50503a4..67524c7c656 100644
--- a/src/site/xdoc/doc_validation.xml
+++ b/src/site/xdoc/doc_validation.xml
@@ -14,12 +14,6 @@
sections below.
- -
- Resource Validation
- is validation of the raw or parsed resource against
- the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet)
- as well as against custom profiles which have been developed.
-
-
Parser Validation
is validation at runtime during the parsing
@@ -31,10 +25,77 @@
that no data is being lost during parsing, but is less comprehensive than resource validation
against raw text data.
+ -
+ Resource Validation
+ is validation of the raw or parsed resource against
+ the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet)
+ as well as against custom profiles which have been developed.
+
+
+
+
+
+ Parser validation is controlled by calling
+ setParserErrorHandler(IParserErrorHandler)
+ on
+ either the FhirContext or on individual parser instances. This method
+ takes an
+ IParserErrorHandler
+ , which is a callback that
+ will be invoked any time a parse issue is detected.
+
+
+ There are two implementations of
+ IParserErrorHandler
+ worth
+ mentioning. You can also supply your own implementation if you want.
+
+
+ -
+ LenientErrorHandler
+ logs any errors but does not abort parsing. By default this handler is used, and it
+ logs errors at "warning" level. It can also be configured to silently
+ ignore issues.
+
+ -
+ StrictErrorHandler
+ throws a
+
DataFormatException
+ if any errors are detected.
+
+
+
+
+ The following example shows how to configure a parser to use strict validation.
+
+
+
+
+
+
+
+ You can also configure the error handler at the FhirContext level, which is useful
+ for clients.
+
+
+
+
+
+
+
+ FhirContext level validators can also be useful on servers.
+
+
+
+
+
+
+
+
-
-
-
- Parser validation is controlled by calling
- setParserErrorHandler(IParserErrorHandler)
- on
- either the FhirContext or on individual parser instances. This method
- takes an
- IParserErrorHandler
- , which is a callback that
- will be invoked any time a parse issue is detected.
-
-
- There are two implementations of
- IParserErrorHandler
- worth
- mentioning. You can also supply your own implementation if you want.
-
-
- -
- LenientErrorHandler
- logs any errors but does not abort parsing. By default this handler is used, and it
- logs errors at "warning" level. It can also be configured to silently
- ignore issues.
-
- -
- StrictErrorHandler
- throws a
-
DataFormatException
- if any errors are detected.
-
-
-
-
- The following example shows how to configure a parser to use strict validation.
-
-
-
-
-
-
-
- You can also configure the error handler at the FhirContext level, which is useful
- for clients.
-
-
-
-
-
-
-
- FhirContext level validators can also be useful on servers.
-
-
-
-
-
-
-
-