From 2741f1cae528466cfdff478072509078fe98cfe5 Mon Sep 17 00:00:00 2001 From: Joseph Athman Date: Wed, 15 Oct 2014 12:26:30 -0500 Subject: [PATCH] Fixes #32 Creates new methods that return a ValidationResult object instead of throwing an exception which requires catching and inspecting the exception itself. Migrating from the old to the new API should be pretty simple as the method calls match quite closely. --- .../ca/uhn/fhir/validation/FhirValidator.java | 87 +++++++++++------- .../uhn/fhir/validation/ValidationResult.java | 72 +++++++++++++++ .../validation/ResourceValidatorTest.java | 92 ++++++++++++++----- .../fhir/validation/ValidationResultTest.java | 46 ++++++++++ 4 files changed, 240 insertions(+), 57 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/FhirValidator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/FhirValidator.java index cdd8ee28349..652d89941c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/FhirValidator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/FhirValidator.java @@ -20,18 +20,15 @@ package ca.uhn.fhir.validation; * #L% */ -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.apache.commons.lang3.Validate; - -import com.phloc.schematron.ISchematronResource; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, etc.) @@ -133,25 +130,18 @@ public class FhirValidator { /** * Validates a bundle instance, throwing a {@link ValidationFailureException} if the validation fails. This validation includes validation of all resources in the bundle. * - * @param theResource + * @param theBundle * The resource to validate * @throws ValidationFailureException * If the validation fails + * @deprecated use {@link #validateWithResult(ca.uhn.fhir.model.api.Bundle)} instead */ + @Deprecated public void validate(Bundle theBundle) { - Validate.notNull(theBundle, "theBundle must not be null"); - - ValidationContext ctx = ValidationContext.forBundle(myContext, theBundle); - - for (IValidator next : myValidators) { - next.validateBundle(ctx); - } - - OperationOutcome oo = ctx.getOperationOutcome(); - if (oo != null && oo.getIssue().size() > 0) { - throw new ValidationFailureException(oo); - } - + ValidationResult validationResult = validateWithResult(theBundle); + if (!validationResult.isSuccessful()) { + throw new ValidationFailureException(validationResult.getOperationOutcome()); + } } /** @@ -161,21 +151,54 @@ public class FhirValidator { * The resource to validate * @throws ValidationFailureException * If the validation fails + * @deprecated use {@link #validateWithResult(ca.uhn.fhir.model.api.IResource)} instead */ + @Deprecated public void validate(IResource theResource) throws ValidationFailureException { - Validate.notNull(theResource, "theResource must not be null"); + ValidationResult validationResult = validateWithResult(theResource); + if (!validationResult.isSuccessful()) { + throw new ValidationFailureException(validationResult.getOperationOutcome()); + } + } - ValidationContext ctx = ValidationContext.forResource(myContext, theResource); + /** + * Validates a bundle instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. + * This validation includes validation of all resources in the bundle. + * + * @param theBundle the bundle to validate + * @return the results of validation + * @since 0.7 + */ + public ValidationResult validateWithResult(Bundle theBundle) { + Validate.notNull(theBundle, "theBundle must not be null"); - for (IValidator next : myValidators) { - next.validateResource(ctx); - } + ValidationContext ctx = ValidationContext.forBundle(myContext, theBundle); - OperationOutcome oo = ctx.getOperationOutcome(); - if (oo != null && oo.getIssue().size() > 0) { - throw new ValidationFailureException(oo); - } + for (IValidator next : myValidators) { + next.validateBundle(ctx); + } - } + OperationOutcome oo = ctx.getOperationOutcome(); + return ValidationResult.valueOf(oo); + } + /** + * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. + * + * @param theResource the resource to validate + * @return the results of validation + * @since 0.7 + */ + public ValidationResult validateWithResult(IResource theResource) { + Validate.notNull(theResource, "theResource must not be null"); + + ValidationContext ctx = ValidationContext.forResource(myContext, theResource); + + for (IValidator next : myValidators) { + next.validateResource(ctx); + } + + OperationOutcome oo = ctx.getOperationOutcome(); + return ValidationResult.valueOf(oo); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java new file mode 100644 index 00000000000..06126f1fdcc --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.validation; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.model.dstu.resource.OperationOutcome; + +/** + * Encapsulates the results of validation + * + * @see ca.uhn.fhir.validation.FhirValidator + * @since 0.7 + */ +public class ValidationResult { + private OperationOutcome myOperationOutcome; + + private ValidationResult(OperationOutcome myOperationOutcome) { + this.myOperationOutcome = myOperationOutcome; + } + + public static ValidationResult valueOf(OperationOutcome myOperationOutcome) { + return new ValidationResult(myOperationOutcome); + } + + public OperationOutcome getOperationOutcome() { + return myOperationOutcome; + } + + @Override + public String toString() { + return "ValidationResult{" + + "myOperationOutcome=" + myOperationOutcome + + ", description='" + toDescription() + '\'' + + '}'; + } + + private String toDescription() { + StringBuilder b = new StringBuilder(100); + if (myOperationOutcome != null) { + OperationOutcome.Issue issueFirstRep = myOperationOutcome.getIssueFirstRep(); + b.append(issueFirstRep.getDetails().getValue()); + b.append(" - "); + b.append(issueFirstRep.getLocationFirstRep().getValue()); + } + return b.toString(); + } + + /** + * Was the validation successful + * @return true if the validation was successful + */ + public boolean isSuccessful() { + return myOperationOutcome == null || myOperationOutcome.getIssue().isEmpty(); + } +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java index e61b1329867..9ec1ebdf5b5 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ResourceValidatorTest.java @@ -1,17 +1,22 @@ package ca.uhn.fhir.validation; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.IOException; - -import org.apache.commons.io.IOUtils; -import org.junit.Test; - import ca.uhn.fhir.context.FhirContext; 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 org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceValidatorTest { @@ -45,9 +50,7 @@ public class ResourceValidatorTest { String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml")); Bundle b = ourCtx.newXmlParser().parseBundle(res); - FhirValidator val = ourCtx.newValidator(); - val.setValidateAgainstStandardSchema(true); - val.setValidateAgainstStandardSchematron(true); + FhirValidator val = createFhirValidator(); val.validate(b); @@ -64,7 +67,6 @@ public class ResourceValidatorTest { } } - @Test public void testSchematronResourceValidator() throws IOException { String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.xml")); @@ -74,20 +76,60 @@ public class ResourceValidatorTest { val.setValidateAgainstStandardSchema(false); val.setValidateAgainstStandardSchematron(true); - val.validate(p); + ValidationResult validationResult = val.validateWithResult(p); + assertTrue(validationResult.isSuccessful()); + + p.getTelecomFirstRep().setValue("123-4567"); + validationResult = val.validateWithResult(p); + assertFalse(validationResult.isSuccessful()); + 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"); - try { - val.validate(p); - fail(); - } catch (ValidationFailureException e) { - ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); - assertEquals(1, e.getOperationOutcome().getIssue().size()); - assertThat(e.getOperationOutcome().getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided.")); - } - p.getTelecomFirstRep().setSystem(ContactSystemEnum.EMAIL); - val.validate(p); + 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); + + FhirValidator val = createFhirValidator(); + + ValidationResult result = val.validateWithResult(b); + assertTrue(result.isSuccessful()); + 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); + + FhirValidator val = createFhirValidator(); + + 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 = 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; + } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java new file mode 100644 index 00000000000..2fe2979119e --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.validation; + +import ca.uhn.fhir.model.dstu.resource.OperationOutcome; +import org.junit.Test; + +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class ValidationResultTest { + + @Test + public void isSuccessful_IsTrueForNullOperationOutcome() { + ValidationResult result = ValidationResult.valueOf(null); + assertTrue(result.isSuccessful()); + } + + @Test + public void isSuccessful_IsTrueForNoIssues() { + OperationOutcome operationOutcome = new OperationOutcome(); + // make sure a non-null ID doesn't cause the validation result to be a fail + operationOutcome.setId(UUID.randomUUID().toString()); + ValidationResult result = ValidationResult.valueOf(operationOutcome); + assertTrue(result.isSuccessful()); + } + + @Test + public void isSuccessful_FalseForIssues() { + OperationOutcome operationOutcome = new OperationOutcome(); + OperationOutcome.Issue issue = operationOutcome.addIssue(); + String errorMessage = "There was a validation problem"; + issue.setDetails(errorMessage); + ValidationResult result = ValidationResult.valueOf(operationOutcome); + assertFalse(result.isSuccessful()); + List issues = result.getOperationOutcome().getIssue(); + assertEquals(1, issues.size()); + assertEquals(errorMessage, issues.get(0).getDetails().getValue()); + + assertThat("ValidationResult#toString should contain the issue description", result.toString(), containsString(errorMessage)); + } +} \ No newline at end of file