Work on common FluentPath interface

This commit is contained in:
James Agnew 2016-11-21 15:52:17 +01:00
parent 5846ce4518
commit 03935be97f
13 changed files with 478 additions and 118 deletions

View File

@ -23,34 +23,23 @@ import java.lang.reflect.Method;
*/
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.view.ViewGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.parser.JsonParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.parser.XmlParser;
import ca.uhn.fhir.parser.*;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
@ -106,6 +95,52 @@ public class FhirContext {
private final IFhirVersion myVersion;
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
private boolean myInitializing;
private IContextValidationSupport<?, ?, ?, ?, ?, ?> myValidationSupport;
/**
* Returns the validation support module configured for this context, creating a default
* implementation if no module has been passed in via the {@link #setValidationSupport(IContextValidationSupport)}
* method
* @see #setValidationSupport(IContextValidationSupport)
*/
public IContextValidationSupport<?, ?, ?, ?, ?, ?> getValidationSupport() {
if (myValidationSupport == null) {
myValidationSupport = myVersion.createValidationSupport();
}
return myValidationSupport;
}
/**
* Creates a new FluentPath engine which can be used to exvaluate
* path expressions over FHIR resources. Note that this engine will use the
* {@link IContextValidationSupport context validation support} module which is
* configured on the context at the time this method is called.
* <p>
* In other words, call {@link #setValidationSupport(IContextValidationSupport)} before
* calling {@link #newFluentPath()}
* </p>
* <p>
* Note that this feature was added for FHIR DSTU3 and is not available
* for contexts configured to use an older version of FHIR. Calling this method
* on a context for a previous version of fhir will result in an
* {@link UnsupportedOperationException}
* </p>
*
* @since 2.2
*/
public IFluentPath newFluentPath() {
return myVersion.createFluentPathExecutor(this);
}
/**
* Sets the validation support module to use for this context. The validation support module
* is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc)
* as well as to provide terminology services to modules such as the validator and FluentPath executor
*/
public void setValidationSupport(IContextValidationSupport<?, ?, ?, ?, ?, ?> theValidationSupport) {
myValidationSupport = theValidationSupport;
}
private ParserOptions myParserOptions = new ParserOptions();
/**

View File

@ -0,0 +1,116 @@
package ca.uhn.fhir.context.support;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST> {
/**
* Expands the given portion of a ValueSet
*
* @param theInclude
* The portion to include
* @return The expansion
*/
EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude);
/**
* Load and return all possible structure definitions
*/
List<SDT> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by ID
*
* @param theSystem
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
CST fetchCodeSystem(FhirContext theContext, String theSystem);
/**
* Loads a resource needed by the validation (a StructureDefinition, or a
* ValueSet)
*
* @param theContext
* The HAPI FHIR Context object current in use by the validator
* @param theClass
* The type of the resource to load
* @param theUri
* The resource URI
* @return Returns the resource, or <code>null</code> if no resource with the
* given URI can be found
*/
<T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
SDT fetchStructureDefinition(FhirContext theCtx, String theUrl);
/**
* Returns <code>true</code> if codes in the given code system can be expanded
* or validated
*
* @param theSystem
* The URI for the code system, e.g. <code>"http://loinc.org"</code>
* @return Returns <code>true</code> if codes in the given code system can be
* validated
*/
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
* binding fields (e.g. <code>Observation.code</code> in the default profile.
*
* @param theCodeSystem
* The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode
* The code, e.g. "<code>1234-5</code>"
* @param theDisplay
* The display name, if it should also be validated
* @return Returns a validation result object
*/
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
public class CodeValidationResult<CDCT, IST> {
private CDCT definition;
private String message;
private IST severity;
public CodeValidationResult(CDCT theNext) {
this.definition = theNext;
}
public CodeValidationResult(IST severity, String message) {
this.severity = severity;
this.message = message;
}
public CodeValidationResult(IST severity, String message, CDCT definition) {
this.severity = severity;
this.message = message;
this.definition = definition;
}
public CDCT asConceptDefinition() {
return definition;
}
public String getMessage() {
return message;
}
public IST getSeverity() {
return severity;
}
public boolean isOk() {
return definition != null;
}
}
}

View File

@ -0,0 +1,21 @@
package ca.uhn.fhir.fluentpath;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
* This exception is thrown if a FluentPath expression can not be executed successfully
* for any reason
*/
public class FluentPathExecutionException extends InternalErrorException {
private static final long serialVersionUID = 1L;
public FluentPathExecutionException(Throwable theCause) {
super(theCause);
}
public FluentPathExecutionException(String theMessage) {
super(theMessage);
}
}

View File

@ -0,0 +1,21 @@
package ca.uhn.fhir.fluentpath;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBase;
public interface IFluentPath {
/**
* Apply the given FluentPath expression against the given input and return
* all results in a list
*
* @param theInput The input object (generally a resource or datatype)
* @param thePath The fluent path expression
* @param theReturnType The type to return (in order to avoid casting)
*/
<T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType);
}

View File

@ -31,6 +31,8 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
@ -62,4 +64,8 @@ public interface IFhirVersion {
IIdType newIdType();
IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport();
IFluentPath createFluentPathExecutor(FhirContext theFhirContext);
}

View File

@ -141,7 +141,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
retVal.setFound(true);
retVal.setSearchedForCode(code);
retVal.setSearchedForSystem(system);
retVal.setCodeDisplay(result.getDisplay());
retVal.setCodeDisplay(result.asConceptDefinition().getDisplay());
retVal.setCodeSystemDisplayName("Unknown");
retVal.setCodeSystemVersion("");
return retVal;

View File

@ -59,6 +59,8 @@ import ca.uhn.fhir.context.RuntimeCompositeDatatypeDefinition;
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimeResourceBlockDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
@ -393,5 +395,15 @@ public class FhirDstu1 implements IFhirVersion {
return new IdDt();
}
@Override
public IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport() {
throw new UnsupportedOperationException("Validation support is not supported in DSTU1 contexts");
}
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU1 contexts");
}
}

View File

@ -32,6 +32,8 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -131,4 +133,15 @@ public class FhirDstu2 implements IFhirVersion {
return new IdDt();
}
@Override
public IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport() {
throw new UnsupportedOperationException("Validation support is not supported in DSTU2 contexts");
}
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
}

View File

@ -25,9 +25,11 @@ import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.hapi.fluentpath.FluentPathDstu3;
import org.hl7.fhir.dstu3.hapi.rest.server.Dstu3BundleFactory;
import org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider;
import org.hl7.fhir.dstu3.hapi.rest.server.ServerProfileProvider;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Reference;
@ -43,6 +45,8 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -131,4 +135,14 @@ public class FhirDstu3 implements IFhirVersion {
return new IdType();
}
@Override
public IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport() {
return new DefaultProfileValidationSupport();
}
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
return new FluentPathDstu3(theFhirContext);
}
}

View File

@ -0,0 +1,47 @@
package org.hl7.fhir.dstu3.hapi.fluentpath;
import java.util.List;
import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.utils.FluentPathEngine;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
import ca.uhn.fhir.fluentpath.IFluentPath;
public class FluentPathDstu3 implements IFluentPath {
private FluentPathEngine myEngine;
public FluentPathDstu3(FhirContext theCtx) {
if (!(theCtx.getValidationSupport() instanceof IValidationSupport)) {
throw new IllegalStateException("Validation support module configured on context appears to be for the wrong FHIR version- Does not extend " + IValidationSupport.class.getName());
}
IValidationSupport validationSupport = (IValidationSupport) theCtx.getValidationSupport();
myEngine = new FluentPathEngine(new HapiWorkerContext(theCtx, validationSupport));
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType) {
List<Base> result;
try {
result = myEngine.evaluate((Base)theInput, thePath);
} catch (FHIRException e) {
throw new FluentPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
return (List<T>) result;
}
}

View File

@ -11,8 +11,10 @@ import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IContextValidationSupport;
public interface IValidationSupport {
public interface IValidationSupport
extends ca.uhn.fhir.context.support.IContextValidationSupport<ConceptSetComponent, ValueSetExpansionComponent, StructureDefinition, CodeSystem, ConceptDefinitionComponent, IssueSeverity> {
/**
* Expands the given portion of a ValueSet
@ -21,14 +23,15 @@ public interface IValidationSupport {
* The portion to include
* @return The expansion
*/
@Override
ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude);
/**
* Load and return all possible structure definitions
*/
@Override
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by ID
*
@ -36,6 +39,7 @@ public interface IValidationSupport {
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem);
/**
@ -51,8 +55,10 @@ public interface IValidationSupport {
* @return Returns the resource, or <code>null</code> if no resource with the
* given URI can be found
*/
@Override
<T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
@Override
StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl);
/**
@ -64,6 +70,7 @@ public interface IValidationSupport {
* @return Returns <code>true</code> if codes in the given code system can be
* validated
*/
@Override
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
@ -79,46 +86,21 @@ public interface IValidationSupport {
* The display name, if it should also be validated
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
public class CodeValidationResult {
private ConceptDefinitionComponent definition;
private String message;
private IssueSeverity severity;
public class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {
public CodeValidationResult(ConceptDefinitionComponent theNext) {
this.definition = theNext;
super(theNext);
}
public CodeValidationResult(IssueSeverity severity, String message) {
this.severity = severity;
this.message = message;
public CodeValidationResult(IssueSeverity theSeverity, String theMessage) {
super(theSeverity, theMessage);
}
public CodeValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
this.severity = severity;
this.message = message;
this.definition = definition;
}
public ConceptDefinitionComponent asConceptDefinition() {
return definition;
}
public String getDisplay() {
return definition == null ? "??" : definition.getDisplay();
}
public String getMessage() {
return message;
}
public IssueSeverity getSeverity() {
return severity;
}
public boolean isOk() {
return definition != null;
super(severity, message, definition);
}
}

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.fluentpath;
import static org.junit.Assert.*;
import java.util.List;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.Patient;
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.util.TestUtil;
public class FluentPathTest {
@Test
public void testEvaluateNormal() {
Patient p = new Patient();
p.addName().addFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().addFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
List<HumanName> names = fp.evaluate(p, "Patient.name", HumanName.class);
assertEquals(2, names.size());
assertEquals("N1F1", names.get(0).getFamilyAsSingleString());
assertEquals("N1G1 N1G2", names.get(0).getGivenAsSingleString());
assertEquals("N2F1", names.get(1).getFamilyAsSingleString());
assertEquals("N2G1 N2G2", names.get(1).getGivenAsSingleString());
}
@Test
public void testEvaluateUnknownPath() {
Patient p = new Patient();
p.addName().addFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().addFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
List<HumanName> names = fp.evaluate(p, "Patient.nameFOO", HumanName.class);
assertEquals(0, names.size());
}
@Test
public void testEvaluateInvalidPath() {
Patient p = new Patient();
p.addName().addFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().addFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient....nameFOO", HumanName.class);
} catch (FluentPathExecutionException e) {
assertEquals("org.hl7.fhir.dstu3.utils.FHIRLexer$FHIRLexerException: Error in Patient....nameFOO at 1, 1: Found . expecting a token name", e.getMessage());
}
}
@Test
public void testEvaluateWrongType() {
Patient p = new Patient();
p.addName().addFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().addFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient.name", StringType.class);
} catch (FluentPathExecutionException e) {
assertEquals("FluentPath expression \"Patient.name\" returned unexpected type HumanName - Expected org.hl7.fhir.dstu3.model.StringType", e.getMessage());
}
}
private static FhirContext ourCtx = FhirContext.forDstu3();
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -39,6 +39,8 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -129,4 +131,16 @@ public class FhirDstu2Hl7Org implements IFhirVersion {
return new IdType();
}
@Override
public IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport() {
throw new UnsupportedOperationException("Validation support is not supported in DSTU2 contexts");
}
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
}