Merge pull request #812 from hapifhir/gg-202205-fhirpath
extend FHIRPath to support lowBoundary(), highBoundary() and precision()
This commit is contained in:
commit
18aff2cf6c
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,776 +0,0 @@
|
|||
package org.hl7.fhir.r4b.context;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.fhir.ucum.UcumService;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.TerminologyServiceException;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext.CodingValidationRequest;
|
||||
import org.hl7.fhir.r4b.context.TerminologyCache.CacheToken;
|
||||
import org.hl7.fhir.r4b.formats.IParser;
|
||||
import org.hl7.fhir.r4b.formats.ParserType;
|
||||
import org.hl7.fhir.r4b.model.Bundle;
|
||||
import org.hl7.fhir.r4b.model.CanonicalResource;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r4b.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4b.model.Coding;
|
||||
import org.hl7.fhir.r4b.model.ConceptMap;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
import org.hl7.fhir.r4b.model.Parameters;
|
||||
import org.hl7.fhir.r4b.model.Resource;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.model.StructureMap;
|
||||
import org.hl7.fhir.r4b.model.ValueSet;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r4b.utils.IResourceValidator;
|
||||
import org.hl7.fhir.utilities.TimeTracker;
|
||||
import org.hl7.fhir.utilities.TranslationServices;
|
||||
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
|
||||
/**
|
||||
* This is the standard interface used for access to underlying FHIR
|
||||
* services through the tools and utilities provided by the reference
|
||||
* implementation.
|
||||
*
|
||||
* The functionality it provides is
|
||||
* - get access to parsers, validators, narrative builders etc
|
||||
* (you can't create these directly because they need access
|
||||
* to the right context for their information)
|
||||
*
|
||||
* - find resources that the tools need to carry out their tasks
|
||||
*
|
||||
* - provide access to terminology services they need.
|
||||
* (typically, these terminology service requests are just
|
||||
* passed through to the local implementation's terminology
|
||||
* service)
|
||||
*
|
||||
* @author Grahame
|
||||
*/
|
||||
public interface IWorkerContext {
|
||||
|
||||
public class CodingValidationRequest {
|
||||
private Coding coding;
|
||||
private ValidationResult result;
|
||||
private CacheToken cacheToken;
|
||||
|
||||
public CodingValidationRequest(Coding coding) {
|
||||
super();
|
||||
this.coding = coding;
|
||||
}
|
||||
|
||||
public ValidationResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(ValidationResult result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Coding getCoding() {
|
||||
return coding;
|
||||
}
|
||||
|
||||
public boolean hasResult() {
|
||||
return result != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* internal logic; external users of batch validation should ignore this property
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CacheToken getCacheToken() {
|
||||
return cacheToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* internal logic; external users of batch validation should ignore this property
|
||||
*
|
||||
* @param cacheToken
|
||||
*/
|
||||
public void setCacheToken(CacheToken cacheToken) {
|
||||
this.cacheToken = cacheToken;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class PackageVersion {
|
||||
private String id;
|
||||
private String version;
|
||||
|
||||
public PackageVersion(String source) {
|
||||
if (source == null) {
|
||||
throw new Error("Source cannot be null");
|
||||
}
|
||||
if (!source.contains("#")) {
|
||||
throw new FHIRException("Source ");
|
||||
}
|
||||
id = source.substring(0, source.indexOf("#"));
|
||||
version = source.substring(source.indexOf("#")+1);
|
||||
}
|
||||
public PackageVersion(String id, String version) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
}
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IContextResourceLoader {
|
||||
/**
|
||||
* @return List of the resource types that shoud be loaded
|
||||
*/
|
||||
String[] getTypes();
|
||||
|
||||
/**
|
||||
* Request to actually load the resources and do whatever is required
|
||||
*
|
||||
* @param stream
|
||||
* @param isJson
|
||||
* @return A bundle because some single resources become multiple resources after loading
|
||||
* @throws FHIRException
|
||||
* @throws IOException
|
||||
*/
|
||||
Bundle loadBundle(InputStream stream, boolean isJson) throws FHIRException, IOException;
|
||||
|
||||
/**
|
||||
* Load a single resources (lazy load)
|
||||
*
|
||||
* @param stream
|
||||
* @param isJson
|
||||
* @return
|
||||
* @throws FHIRException - throw this if you a single resource can't be returned - can't lazy load in this circumstance
|
||||
* @throws IOException
|
||||
*/
|
||||
Resource loadResource(InputStream stream, boolean isJson) throws FHIRException, IOException;
|
||||
|
||||
/**
|
||||
* get the path for references to this resource.
|
||||
* @param resource
|
||||
* @return null if not tracking paths
|
||||
*/
|
||||
String getResourcePath(Resource resource);
|
||||
|
||||
/**
|
||||
* called when a mew package is being loaded
|
||||
*
|
||||
* this is called by loadPacakgeAndDependencies when a new package is loaded
|
||||
* @param npm
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws JsonSyntaxException
|
||||
*/
|
||||
IContextResourceLoader getNewLoader(NpmPackage npm) throws JsonSyntaxException, IOException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the versions of the definitions loaded in context
|
||||
* @return
|
||||
*/
|
||||
public String getVersion();
|
||||
public String getSpecUrl();
|
||||
|
||||
// get the UCUM service (might not be available)
|
||||
public UcumService getUcumService();
|
||||
|
||||
// -- Parsers (read and write instances) ----------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Get a parser to read/write instances. Use the defined type (will be extended
|
||||
* as further types are added, though the only currently anticipate type is RDF)
|
||||
*
|
||||
* XML/JSON - the standard renderers
|
||||
* XHTML - render the narrative only (generate it if necessary)
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public IParser getParser(ParserType type);
|
||||
|
||||
/**
|
||||
* Get a parser to read/write instances. Determine the type
|
||||
* from the stated type. Supported value for type:
|
||||
* - the recommended MIME types
|
||||
* - variants of application/xml and application/json
|
||||
* - _format values xml, json
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public IParser getParser(String type);
|
||||
|
||||
/**
|
||||
* Get a JSON parser
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public IParser newJsonParser();
|
||||
|
||||
/**
|
||||
* Get an XML parser
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public IParser newXmlParser();
|
||||
|
||||
/**
|
||||
* Get a validator that can check whether a resource is valid
|
||||
*
|
||||
* @return a prepared generator
|
||||
* @throws FHIRException
|
||||
* @
|
||||
*/
|
||||
public IResourceValidator newValidator() throws FHIRException;
|
||||
|
||||
// -- resource fetchers ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Find an identified resource. The most common use of this is to access the the
|
||||
* standard conformance resources that are part of the standard - structure
|
||||
* definitions, value sets, concept maps, etc.
|
||||
*
|
||||
* Also, the narrative generator uses this, and may access any kind of resource
|
||||
*
|
||||
* The URI is called speculatively for things that might exist, so not finding
|
||||
* a matching resouce, return null, not an error
|
||||
*
|
||||
* The URI can have one of 3 formats:
|
||||
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
|
||||
* - a relative URL e.g. ValueSet/[id]
|
||||
* - a logical id e.g. [id]
|
||||
*
|
||||
* It's an error if the second form doesn't agree with class_. It's an
|
||||
* error if class_ is null for the last form
|
||||
*
|
||||
* @param resource
|
||||
* @param Reference
|
||||
* @return
|
||||
* @throws FHIRException
|
||||
* @throws Exception
|
||||
*/
|
||||
public <T extends Resource> T fetchResource(Class<T> class_, String uri);
|
||||
public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException;
|
||||
|
||||
/** has the same functionality as fetchResource, but passes in information about the source of the
|
||||
* reference (this may affect resolution of version)
|
||||
*
|
||||
* @param <T>
|
||||
* @param class_
|
||||
* @param uri
|
||||
* @param canonicalForSource
|
||||
* @return
|
||||
*/
|
||||
public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource canonicalForSource);
|
||||
|
||||
/**
|
||||
* Variation of fetchResource when you have a string type, and don't need the right class
|
||||
*
|
||||
* The URI can have one of 3 formats:
|
||||
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
|
||||
* - a relative URL e.g. ValueSet/[id]
|
||||
* - a logical id e.g. [id]
|
||||
*
|
||||
* if type == null, the URI can't be a simple logical id
|
||||
*
|
||||
* @param type
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public Resource fetchResourceById(String type, String uri);
|
||||
|
||||
/**
|
||||
* find whether a resource is available.
|
||||
*
|
||||
* Implementations of the interface can assume that if hasResource ruturns
|
||||
* true, the resource will usually be fetched subsequently
|
||||
*
|
||||
* @param class_
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public <T extends Resource> boolean hasResource(Class<T> class_, String uri);
|
||||
|
||||
/**
|
||||
* cache a resource for later retrieval using fetchResource.
|
||||
*
|
||||
* Note that various context implementations will have their own ways of loading
|
||||
* rseources, and not all need implement cacheResource.
|
||||
*
|
||||
* If the resource is loaded out of a package, call cacheResourceFromPackage instead
|
||||
* @param res
|
||||
* @throws FHIRException
|
||||
*/
|
||||
public void cacheResource(Resource res) throws FHIRException;
|
||||
|
||||
/**
|
||||
* cache a resource for later retrieval using fetchResource.
|
||||
*
|
||||
* The package information is used to help manage the cache internally, and to
|
||||
* help with reference resolution. Packages should be define using cachePackage (but don't have to be)
|
||||
*
|
||||
* Note that various context implementations will have their own ways of loading
|
||||
* rseources, and not all need implement cacheResource
|
||||
*
|
||||
* @param res
|
||||
* @throws FHIRException
|
||||
*/
|
||||
public void cacheResourceFromPackage(Resource res, PackageVersion packageDetails) throws FHIRException;
|
||||
|
||||
/**
|
||||
* Inform the cache about package dependencies. This can be used to help resolve references
|
||||
*
|
||||
* Note that the cache doesn't load dependencies
|
||||
*
|
||||
* @param packageInfo
|
||||
*/
|
||||
public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies);
|
||||
|
||||
// -- profile services ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @return a list of the resource names defined for this version
|
||||
*/
|
||||
public List<String> getResourceNames();
|
||||
/**
|
||||
* @return a set of the resource names defined for this version
|
||||
*/
|
||||
public Set<String> getResourceNamesAsSet();
|
||||
|
||||
/**
|
||||
* @return a list of the resource and type names defined for this version
|
||||
*/
|
||||
public List<String> getTypeNames();
|
||||
|
||||
/**
|
||||
* @return a list of all structure definitions, with snapshots generated (if possible)
|
||||
*/
|
||||
public List<StructureDefinition> allStructures();
|
||||
|
||||
/**
|
||||
* @return a list of all structure definitions, without trying to generate snapshots
|
||||
*/
|
||||
public List<StructureDefinition> getStructures();
|
||||
|
||||
/**
|
||||
* @return a list of all conformance resources
|
||||
*/
|
||||
public List<CanonicalResource> allConformanceResources();
|
||||
|
||||
/**
|
||||
* Given a structure definition, generate a snapshot (or regenerate it)
|
||||
* @param p
|
||||
* @throws DefinitionException
|
||||
* @throws FHIRException
|
||||
*/
|
||||
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException;
|
||||
public void generateSnapshot(StructureDefinition mr, boolean ifLogical);
|
||||
|
||||
// -- Terminology services ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the expansion parameters passed through the terminology server when txServer calls are made
|
||||
*
|
||||
* Note that the Validation Options override these when they are specified on validateCode
|
||||
*/
|
||||
public Parameters getExpansionParameters();
|
||||
|
||||
/**
|
||||
* Get the expansion parameters passed through the terminology server when txServer calls are made
|
||||
*
|
||||
* Note that the Validation Options override these when they are specified on validateCode
|
||||
*/
|
||||
public void setExpansionProfile(Parameters expParameters);
|
||||
|
||||
// these are the terminology services used internally by the tools
|
||||
/**
|
||||
* Find the code system definition for the nominated system uri.
|
||||
* return null if there isn't one (then the tool might try
|
||||
* supportsSystem)
|
||||
*
|
||||
* @param system
|
||||
* @return
|
||||
*/
|
||||
public CodeSystem fetchCodeSystem(String system);
|
||||
|
||||
/**
|
||||
* True if the underlying terminology service provider will do
|
||||
* expansion and code validation for the terminology. Corresponds
|
||||
* to the extension
|
||||
*
|
||||
* http://hl7.org/fhir/StructureDefinition/capabilitystatement-supported-system
|
||||
*
|
||||
* in the Conformance resource
|
||||
*
|
||||
* @param system
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public boolean supportsSystem(String system) throws TerminologyServiceException;
|
||||
|
||||
/**
|
||||
* find concept maps for a source
|
||||
* @param url
|
||||
* @return
|
||||
* @throws FHIRException
|
||||
*/
|
||||
public List<ConceptMap> findMapsForSource(String url) throws FHIRException;
|
||||
|
||||
/**
|
||||
* ValueSet Expansion - see $expand
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
|
||||
|
||||
/**
|
||||
* ValueSet Expansion - see $expand, but resolves the binding first
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
* @throws FHIRException
|
||||
*/
|
||||
public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException;
|
||||
|
||||
/**
|
||||
* Value set expanion inside the internal expansion engine - used
|
||||
* for references to supported system (see "supportsSystem") for
|
||||
* which there is no value set.
|
||||
*
|
||||
* @param inc
|
||||
* @return
|
||||
* @throws FHIRException
|
||||
*/
|
||||
ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical) throws TerminologyServiceException;
|
||||
|
||||
Locale getLocale();
|
||||
|
||||
void setLocale(Locale locale);
|
||||
|
||||
String formatMessage(String theMessage, Object... theMessageArguments);
|
||||
|
||||
void setValidationMessageLanguage(Locale locale);
|
||||
|
||||
class ValidationResult {
|
||||
private ConceptDefinitionComponent definition;
|
||||
private IssueSeverity severity;
|
||||
private String message;
|
||||
private TerminologyServiceErrorClass errorClass;
|
||||
private String txLink;
|
||||
|
||||
public ValidationResult(IssueSeverity severity, String message) {
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ValidationResult(ConceptDefinitionComponent definition) {
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
public ValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
public ValidationResult(IssueSeverity severity, String message, TerminologyServiceErrorClass errorClass) {
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
this.errorClass = errorClass;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return severity == null || severity == IssueSeverity.INFORMATION || severity == IssueSeverity.WARNING;
|
||||
}
|
||||
|
||||
public String getDisplay() {
|
||||
// We don't want to return question-marks because that prevents something more useful from being displayed (e.g. the code) if there's no display value
|
||||
// return definition == null ? "??" : definition.getDisplay();
|
||||
return definition == null ? null : definition.getDisplay();
|
||||
}
|
||||
|
||||
public ConceptDefinitionComponent asConceptDefinition() {
|
||||
return definition;
|
||||
}
|
||||
|
||||
public IssueSeverity getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean IsNoService() {
|
||||
return errorClass == TerminologyServiceErrorClass.NOSERVICE;
|
||||
}
|
||||
|
||||
public TerminologyServiceErrorClass getErrorClass() {
|
||||
return errorClass;
|
||||
}
|
||||
|
||||
public ValidationResult setSeverity(IssueSeverity severity) {
|
||||
this.severity = severity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidationResult setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTxLink() {
|
||||
return txLink;
|
||||
}
|
||||
|
||||
public ValidationResult setTxLink(String txLink) {
|
||||
this.txLink = txLink;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation of a code - consult the terminology infrstructure and/or service
|
||||
* to see whether it is known. If known, return a description of it
|
||||
*
|
||||
* note: always return a result, with either an error or a code description
|
||||
*
|
||||
* corresponds to 2 terminology service calls: $validate-code and $lookup
|
||||
*
|
||||
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
|
||||
*
|
||||
* @param options - validation options (required)
|
||||
* @param code he code to validate (required)
|
||||
* @param vs the applicable valueset (required)
|
||||
* @return
|
||||
*/
|
||||
public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs);
|
||||
|
||||
/**
|
||||
* Validation of a code - consult the terminology infrstructure and/or service
|
||||
* to see whether it is known. If known, return a description of it
|
||||
*
|
||||
* note: always return a result, with either an error or a code description
|
||||
*
|
||||
* corresponds to 2 terminology service calls: $validate-code and $lookup
|
||||
*
|
||||
* @param options - validation options (required)
|
||||
* @param system - equals Coding.system (required)
|
||||
* @param code - equals Coding.code (required)
|
||||
* @param display - equals Coding.display (optional)
|
||||
* @return
|
||||
*/
|
||||
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display);
|
||||
|
||||
/**
|
||||
* Validation of a code - consult the terminology infrstructure and/or service
|
||||
* to see whether it is known. If known, return a description of it
|
||||
*
|
||||
* note: always return a result, with either an error or a code description
|
||||
*
|
||||
* corresponds to 2 terminology service calls: $validate-code and $lookup
|
||||
*
|
||||
* @param options - validation options (required)
|
||||
* @param system - equals Coding.system (required)
|
||||
* @param code - equals Coding.code (required)
|
||||
* @param display - equals Coding.display (optional)
|
||||
* @param vs the applicable valueset (optional)
|
||||
* @return
|
||||
*/
|
||||
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs);
|
||||
|
||||
/**
|
||||
* Validation of a code - consult the terminology infrstructure and/or service
|
||||
* to see whether it is known. If known, return a description of it
|
||||
*
|
||||
* note: always return a result, with either an error or a code description
|
||||
*
|
||||
* corresponds to 2 terminology service calls: $validate-code and $lookup
|
||||
*
|
||||
* Note that this doesn't validate binding strength (e.g. is just text allowed?)
|
||||
*
|
||||
* @param options - validation options (required)
|
||||
* @param code - CodeableConcept to validate
|
||||
* @param vs the applicable valueset (optional)
|
||||
* @return
|
||||
*/
|
||||
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs);
|
||||
|
||||
/**
|
||||
* Validation of a code - consult the terminology infrstructure and/or service
|
||||
* to see whether it is known. If known, return a description of it
|
||||
*
|
||||
* note: always return a result, with either an error or a code description
|
||||
*
|
||||
* corresponds to 2 terminology service calls: $validate-code and $lookup
|
||||
*
|
||||
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
|
||||
*
|
||||
* @param options - validation options (required)
|
||||
* @param code - Coding to validate
|
||||
* @param vs the applicable valueset (optional)
|
||||
* @return
|
||||
*/
|
||||
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs);
|
||||
|
||||
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs);
|
||||
|
||||
/**
|
||||
* returns the recommended tla for the type (from the structure definitions)
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public String getAbbreviation(String name);
|
||||
|
||||
|
||||
/**
|
||||
* translate an OID to a URI (look through known NamingSystems)
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public String oid2Uri(String code);
|
||||
|
||||
/**
|
||||
* @return true if the contxt has a terminology caching service internally
|
||||
*/
|
||||
public boolean hasCache();
|
||||
|
||||
public interface ILoggingService {
|
||||
public enum LogCategory {
|
||||
INIT,
|
||||
PROGRESS,
|
||||
TX,
|
||||
CONTEXT,
|
||||
GENERATE,
|
||||
HTML
|
||||
}
|
||||
public void logMessage(String message); // status messages, always display
|
||||
public void logDebugMessage(LogCategory category, String message); // verbose; only when debugging
|
||||
}
|
||||
|
||||
public void setLogger(ILoggingService logger);
|
||||
public ILoggingService getLogger();
|
||||
|
||||
public boolean isNoTerminologyServer();
|
||||
public Set<String> getCodeSystemsUsed();
|
||||
public TranslationServices translator();
|
||||
public List<StructureMap> listTransforms();
|
||||
public StructureMap getTransform(String url);
|
||||
|
||||
public String getOverrideVersionNs();
|
||||
public void setOverrideVersionNs(String value);
|
||||
|
||||
public StructureDefinition fetchTypeDefinition(String typeName);
|
||||
public StructureDefinition fetchRawProfile(String url);
|
||||
|
||||
public void setUcumService(UcumService ucumService);
|
||||
|
||||
public String getLinkForUrl(String corePath, String s);
|
||||
public Map<String, byte[]> getBinaries();
|
||||
|
||||
/**
|
||||
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
|
||||
*
|
||||
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
|
||||
*
|
||||
* @param pi - the package to load
|
||||
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
|
||||
* @return the number of resources loaded
|
||||
*/
|
||||
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException;
|
||||
|
||||
/**
|
||||
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
|
||||
*
|
||||
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
|
||||
*
|
||||
* Deprecated - use the simpler method where the types come from the loader.
|
||||
*
|
||||
* @param pi - the package to load
|
||||
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
|
||||
* @param types - which types of resources to load
|
||||
* @return the number of resources loaded
|
||||
*/
|
||||
@Deprecated
|
||||
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException;
|
||||
|
||||
/**
|
||||
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
|
||||
*
|
||||
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
|
||||
*
|
||||
* This method also loads all the packages that the package depends on (recursively)
|
||||
*
|
||||
* @param pi - the package to load
|
||||
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
|
||||
* @param pcm - used to find and load additional dependencies
|
||||
* @return the number of resources loaded
|
||||
*/
|
||||
int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException;
|
||||
|
||||
public boolean hasPackage(String id, String ver);
|
||||
|
||||
public int getClientRetryCount();
|
||||
public IWorkerContext setClientRetryCount(int value);
|
||||
|
||||
public TimeTracker clock();
|
||||
}
|
|
@ -1,847 +0,0 @@
|
|||
package org.hl7.fhir.r4b.context;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
|
||||
import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider;
|
||||
import org.hl7.fhir.r4b.context.CanonicalResourceManager.CanonicalResourceProxy;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext.ILoggingService.LogCategory;
|
||||
import org.hl7.fhir.r4b.context.SimpleWorkerContext.PackageResourceLoader;
|
||||
import org.hl7.fhir.r4b.formats.IParser;
|
||||
import org.hl7.fhir.r4b.formats.JsonParser;
|
||||
import org.hl7.fhir.r4b.formats.ParserType;
|
||||
import org.hl7.fhir.r4b.formats.XmlParser;
|
||||
import org.hl7.fhir.r4b.model.Bundle;
|
||||
import org.hl7.fhir.r4b.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.r4b.model.CanonicalResource;
|
||||
import org.hl7.fhir.r4b.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
import org.hl7.fhir.r4b.model.Questionnaire;
|
||||
import org.hl7.fhir.r4b.model.Resource;
|
||||
import org.hl7.fhir.r4b.model.ResourceType;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
|
||||
import org.hl7.fhir.r4b.model.StructureMap;
|
||||
import org.hl7.fhir.r4b.model.StructureMap.StructureMapModelMode;
|
||||
import org.hl7.fhir.r4b.model.StructureMap.StructureMapStructureComponent;
|
||||
import org.hl7.fhir.r4b.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r4b.utils.IResourceValidator;
|
||||
import org.hl7.fhir.r4b.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.CSFileInputStream;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.TimeTracker;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
/*
|
||||
* This is a stand alone implementation of worker context for use inside a tool.
|
||||
* It loads from the validation package (validation-min.xml.zip), and has a
|
||||
* very light client to connect to an open unauthenticated terminology service
|
||||
*/
|
||||
|
||||
public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
|
||||
|
||||
public static class PackageResourceLoader extends CanonicalResourceProxy {
|
||||
|
||||
private String filename;
|
||||
private IContextResourceLoader loader;
|
||||
|
||||
public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) {
|
||||
super(pri.getType(), pri.getId(), pri.getUrl(),pri.getVersion());
|
||||
this.filename = pri.getFilename();
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource loadResource() {
|
||||
try {
|
||||
FileInputStream f = new FileInputStream(filename);
|
||||
try {
|
||||
if (loader != null) {
|
||||
return (CanonicalResource) loader.loadResource(f, true);
|
||||
} else {
|
||||
return (CanonicalResource) new JsonParser().parse(f);
|
||||
}
|
||||
} finally {
|
||||
f.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILoadFilter {
|
||||
boolean isOkToLoad(Resource resource);
|
||||
boolean isOkToLoad(String resourceType);
|
||||
}
|
||||
|
||||
public interface IValidatorFactory {
|
||||
IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException;
|
||||
IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException;
|
||||
}
|
||||
|
||||
private Questionnaire questionnaire;
|
||||
private String revision;
|
||||
private String date;
|
||||
private IValidatorFactory validatorFactory;
|
||||
private boolean ignoreProfileErrors;
|
||||
private boolean progress;
|
||||
private List<String> loadedPackages = new ArrayList<String>();
|
||||
private boolean canNoTS;
|
||||
private XVerExtensionManager xverManager;
|
||||
|
||||
public SimpleWorkerContext() throws FileNotFoundException, IOException, FHIRException {
|
||||
super();
|
||||
}
|
||||
|
||||
public SimpleWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
|
||||
super(locale);
|
||||
}
|
||||
|
||||
public SimpleWorkerContext(SimpleWorkerContext other) throws FileNotFoundException, IOException, FHIRException {
|
||||
super();
|
||||
copy(other);
|
||||
}
|
||||
|
||||
public SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws FileNotFoundException, IOException, FHIRException {
|
||||
super(locale);
|
||||
copy(other);
|
||||
}
|
||||
|
||||
protected void copy(SimpleWorkerContext other) {
|
||||
super.copy(other);
|
||||
questionnaire = other.questionnaire;
|
||||
binaries.putAll(other.binaries);
|
||||
version = other.version;
|
||||
revision = other.revision;
|
||||
date = other.date;
|
||||
validatorFactory = other.validatorFactory;
|
||||
}
|
||||
|
||||
|
||||
public List<String> getLoadedPackages() {
|
||||
return loadedPackages;
|
||||
}
|
||||
|
||||
// -- Initializations
|
||||
/**
|
||||
* Load the working context from the validation pack
|
||||
*
|
||||
* @param path
|
||||
* filename of the validation pack
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws FileNotFoundException
|
||||
* @throws FHIRException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.loadFromPack(path, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromPackage(NpmPackage pi, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.setAllowLoadingDuplicates(allowDuplicates);
|
||||
res.loadFromPackage(pi, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromPackage(NpmPackage pi) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.loadFromPackage(pi, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.setAllowLoadingDuplicates(true);
|
||||
res.version = pi.getNpm().get("version").getAsString();
|
||||
res.loadFromPackage(pi, loader);
|
||||
res.finishLoading();
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromPack(String path, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.setAllowLoadingDuplicates(allowDuplicates);
|
||||
res.loadFromPack(path, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.loadFromPack(path, loader);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
|
||||
return fromClassPath(name, false);
|
||||
}
|
||||
public static SimpleWorkerContext fromClassPath(String name, boolean allowDuplicates) throws IOException, FHIRException {
|
||||
InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.setAllowLoadingDuplicates(allowDuplicates);
|
||||
res.loadFromStream(s, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
for (String name : source.keySet()) {
|
||||
try {
|
||||
res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader, null, pi);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error loading "+name+": "+e.getMessage());
|
||||
throw new FHIRException("Error loading "+name+": "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static SimpleWorkerContext fromNothing() throws FileNotFoundException, FHIRException, IOException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
return res;
|
||||
}
|
||||
|
||||
private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
|
||||
if (name.endsWith(".xml"))
|
||||
loadFromFile(stream, name, loader, filter);
|
||||
else if (name.endsWith(".json"))
|
||||
loadFromFileJson(stream, name, loader, filter, pi);
|
||||
else if (name.equals("version.info"))
|
||||
readVersionInfo(stream);
|
||||
else
|
||||
loadBytes(name, stream);
|
||||
}
|
||||
|
||||
public String connectToTSServer(TerminologyClient client, String log) {
|
||||
try {
|
||||
tlog("Connect to "+client.getAddress());
|
||||
txClient = client;
|
||||
if (log != null && log.endsWith(".txt")) {
|
||||
txLog = new TextClientLogger(log);
|
||||
} else {
|
||||
txLog = new HTMLClientLogger(log);
|
||||
}
|
||||
txClient.setLogger(txLog);
|
||||
CapabilityStatement cps = txClient.getCapabilitiesStatementQuick();
|
||||
setTxCaps(txClient.getTerminologyCapabilities());
|
||||
return cps.getSoftware().getVersion();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws IOException, FHIRException {
|
||||
loadFromFile(stream, name, loader, null);
|
||||
}
|
||||
|
||||
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws IOException, FHIRException {
|
||||
Resource f;
|
||||
try {
|
||||
if (loader != null)
|
||||
f = loader.loadBundle(stream, false);
|
||||
else {
|
||||
XmlParser xml = new XmlParser();
|
||||
f = xml.parse(stream);
|
||||
}
|
||||
} catch (DataFormatException e1) {
|
||||
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
|
||||
} catch (Exception e1) {
|
||||
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
|
||||
}
|
||||
if (f instanceof Bundle) {
|
||||
Bundle bnd = (Bundle) f;
|
||||
for (BundleEntryComponent e : bnd.getEntry()) {
|
||||
if (e.getFullUrl() == null) {
|
||||
logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)");
|
||||
}
|
||||
if (filter == null || filter.isOkToLoad(e.getResource())) {
|
||||
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
|
||||
if (path != null) {
|
||||
e.getResource().setUserData("path", path);
|
||||
}
|
||||
cacheResource(e.getResource());
|
||||
}
|
||||
}
|
||||
} else if (f instanceof CanonicalResource) {
|
||||
if (filter == null || filter.isOkToLoad(f)) {
|
||||
String path = loader != null ? loader.getResourcePath(f) : null;
|
||||
if (path != null) {
|
||||
f.setUserData("path", path);
|
||||
}
|
||||
cacheResource(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
|
||||
Bundle f = null;
|
||||
try {
|
||||
if (loader != null)
|
||||
f = loader.loadBundle(stream, true);
|
||||
else {
|
||||
JsonParser json = new JsonParser();
|
||||
Resource r = json.parse(stream);
|
||||
if (r instanceof Bundle)
|
||||
f = (Bundle) r;
|
||||
else if (filter == null || filter.isOkToLoad(f)) {
|
||||
cacheResourceFromPackage(r, pi);
|
||||
}
|
||||
}
|
||||
} catch (FHIRFormatError e1) {
|
||||
throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
|
||||
}
|
||||
if (f != null)
|
||||
for (BundleEntryComponent e : f.getEntry()) {
|
||||
if (filter == null || filter.isOkToLoad(e.getResource())) {
|
||||
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
|
||||
if (path != null) {
|
||||
e.getResource().setUserData("path", path);
|
||||
}
|
||||
cacheResourceFromPackage(e.getResource(), pi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
|
||||
loadFromStream(new CSFileInputStream(path), loader);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
|
||||
return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes());
|
||||
}
|
||||
|
||||
public static String[] defaultTypesToLoad() {
|
||||
// there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions
|
||||
return new String[] {"CodeSystem", "ValueSet", "ConceptMap", "NamingSystem",
|
||||
"StructureDefinition", "StructureMap",
|
||||
"SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance",
|
||||
"Questionnaire", "ImplementationGuide", "Measure" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException {
|
||||
return loadFromPackageInt(pi, loader, types);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException {
|
||||
return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version());
|
||||
}
|
||||
public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws FileNotFoundException, IOException, FHIRException {
|
||||
int t = 0;
|
||||
|
||||
for (String e : pi.dependencies()) {
|
||||
if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) {
|
||||
NpmPackage npm = pcm.loadPackage(e);
|
||||
if (!version.equals(npm.fhirVersion())) {
|
||||
System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path));
|
||||
}
|
||||
t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version());
|
||||
}
|
||||
}
|
||||
t = t + loadFromPackageInt(pi, loader, loader.getTypes());
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, String... types) throws FileNotFoundException, IOException, FHIRException {
|
||||
int t = 0;
|
||||
if (progress) {
|
||||
System.out.println("Load Package "+pi.name()+"#"+pi.version());
|
||||
}
|
||||
if (loadedPackages.contains(pi.id()+"#"+pi.version())) {
|
||||
return 0;
|
||||
}
|
||||
loadedPackages.add(pi.id()+"#"+pi.version());
|
||||
|
||||
|
||||
if ((types == null || types.length == 0) && loader != null) {
|
||||
types = loader.getTypes();
|
||||
}
|
||||
if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad()) {
|
||||
// can't lazy load R2 because of valueset/codesystem implementation
|
||||
if (types.length == 0) {
|
||||
types = new String[] { "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" };
|
||||
}
|
||||
for (String s : pi.listResources(types)) {
|
||||
try {
|
||||
loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version()));
|
||||
t++;
|
||||
} catch (FHIRException e) {
|
||||
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (types.length == 0) {
|
||||
types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" };
|
||||
}
|
||||
for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
|
||||
try {
|
||||
registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version()));
|
||||
t++;
|
||||
} catch (FHIRException e) {
|
||||
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String s : pi.list("other")) {
|
||||
binaries.put(s, TextFile.streamToBytes(pi.load("other", s)));
|
||||
}
|
||||
if (version == null) {
|
||||
version = pi.version();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
|
||||
loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null);
|
||||
}
|
||||
|
||||
private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
|
||||
ZipInputStream zip = new ZipInputStream(stream);
|
||||
ZipEntry ze;
|
||||
while ((ze = zip.getNextEntry()) != null) {
|
||||
loadDefinitionItem(ze.getName(), zip, loader, null, null);
|
||||
zip.closeEntry();
|
||||
}
|
||||
zip.close();
|
||||
}
|
||||
|
||||
private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
|
||||
byte[] bytes = IOUtils.toByteArray(stream);
|
||||
binaries.put("version.info", bytes);
|
||||
|
||||
String[] vi = new String(bytes).split("\\r?\\n");
|
||||
for (String s : vi) {
|
||||
if (s.startsWith("version=")) {
|
||||
if (version == null)
|
||||
version = s.substring(8);
|
||||
else if (!version.equals(s.substring(8)))
|
||||
throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8)));
|
||||
}
|
||||
if (s.startsWith("revision="))
|
||||
revision = s.substring(9);
|
||||
if (s.startsWith("date="))
|
||||
date = s.substring(5);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadBytes(String name, InputStream stream) throws IOException {
|
||||
byte[] bytes = IOUtils.toByteArray(stream);
|
||||
binaries.put(name, bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser getParser(ParserType type) {
|
||||
switch (type) {
|
||||
case JSON: return newJsonParser();
|
||||
case XML: return newXmlParser();
|
||||
default:
|
||||
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser getParser(String type) {
|
||||
if (type.equalsIgnoreCase("JSON"))
|
||||
return new JsonParser();
|
||||
if (type.equalsIgnoreCase("XML"))
|
||||
return new XmlParser();
|
||||
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser newJsonParser() {
|
||||
return new JsonParser();
|
||||
}
|
||||
@Override
|
||||
public IParser newXmlParser() {
|
||||
return new XmlParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResourceValidator newValidator() throws FHIRException {
|
||||
if (validatorFactory == null)
|
||||
throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
|
||||
return validatorFactory.makeValidator(this, xverManager);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> getResourceNames() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (StructureDefinition sd : listStructures()) {
|
||||
if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
|
||||
result.add(sd.getName());
|
||||
}
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (StructureDefinition sd : listStructures()) {
|
||||
if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
|
||||
result.add(sd.getName());
|
||||
}
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbbreviation(String name) {
|
||||
return "xxx";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDatatype(String typeSimple) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResource(String t) {
|
||||
StructureDefinition sd;
|
||||
try {
|
||||
sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
if (sd == null)
|
||||
return false;
|
||||
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
|
||||
return false;
|
||||
return sd.getKind() == StructureDefinitionKind.RESOURCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLinkFor(String typeSimple) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLinkFor(String corePath, String typeSimple) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLinkForProfile(StructureDefinition profile, String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Questionnaire getQuestionnaire() {
|
||||
return questionnaire;
|
||||
}
|
||||
|
||||
public void setQuestionnaire(Questionnaire questionnaire) {
|
||||
this.questionnaire = questionnaire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StructureDefinition> allStructures() {
|
||||
List<StructureDefinition> result = new ArrayList<StructureDefinition>();
|
||||
Set<StructureDefinition> set = new HashSet<StructureDefinition>();
|
||||
for (StructureDefinition sd : listStructures()) {
|
||||
if (!set.contains(sd)) {
|
||||
try {
|
||||
generateSnapshot(sd);
|
||||
// new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
|
||||
if (true) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
result.add(sd);
|
||||
set.add(sd);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public void loadBinariesFromFolder(String folder) throws FileNotFoundException, Exception {
|
||||
for (String n : new File(folder).list()) {
|
||||
loadBytes(n, new FileInputStream(Utilities.path(folder, n)));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadBinariesFromFolder(NpmPackage pi) throws FileNotFoundException, Exception {
|
||||
for (String n : pi.list("other")) {
|
||||
loadBytes(n, pi.load("other", n));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFromFolder(String folder) throws FileNotFoundException, Exception {
|
||||
for (String n : new File(folder).list()) {
|
||||
if (n.endsWith(".json"))
|
||||
loadFromFile(Utilities.path(folder, n), new JsonParser());
|
||||
else if (n.endsWith(".xml"))
|
||||
loadFromFile(Utilities.path(folder, n), new XmlParser());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromFile(String filename, IParser p) throws FileNotFoundException, Exception {
|
||||
Resource r;
|
||||
try {
|
||||
r = p.parse(new FileInputStream(filename));
|
||||
if (r.getResourceType() == ResourceType.Bundle) {
|
||||
for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
|
||||
cacheResource(e.getResource());
|
||||
}
|
||||
} else {
|
||||
cacheResource(r);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prependLinks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCache() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
public List<StructureMap> findTransformsforSource(String url) {
|
||||
List<StructureMap> res = new ArrayList<StructureMap>();
|
||||
for (StructureMap map : listTransforms()) {
|
||||
boolean match = false;
|
||||
boolean ok = true;
|
||||
for (StructureMapStructureComponent t : map.getStructure()) {
|
||||
if (t.getMode() == StructureMapModelMode.SOURCE) {
|
||||
match = match || t.getUrl().equals(url);
|
||||
ok = ok && t.getUrl().equals(url);
|
||||
}
|
||||
}
|
||||
if (match && ok)
|
||||
res.add(map);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public IValidatorFactory getValidatorFactory() {
|
||||
return validatorFactory;
|
||||
}
|
||||
|
||||
public void setValidatorFactory(IValidatorFactory validatorFactory) {
|
||||
this.validatorFactory = validatorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
|
||||
T r = super.fetchResource(class_, uri);
|
||||
if (r instanceof StructureDefinition) {
|
||||
StructureDefinition p = (StructureDefinition)r;
|
||||
try {
|
||||
generateSnapshot(p);
|
||||
} catch (Exception e) {
|
||||
// not sure what to do in this case?
|
||||
System.out.println("Unable to generate snapshot for "+uri+": "+e.getMessage());
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition fetchRawProfile(String uri) {
|
||||
StructureDefinition r = super.fetchResource(StructureDefinition.class, uri);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
|
||||
generateSnapshot(p, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateSnapshot(StructureDefinition p, boolean logical) throws DefinitionException, FHIRException {
|
||||
if (!p.hasSnapshot() && (logical || p.getKind() != StructureDefinitionKind.LOGICAL)) {
|
||||
if (!p.hasBaseDefinition())
|
||||
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
|
||||
StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition());
|
||||
if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
|
||||
sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
|
||||
}
|
||||
if (sd == null) {
|
||||
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
|
||||
}
|
||||
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
|
||||
List<String> errors = new ArrayList<String>();
|
||||
ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
|
||||
pu.setAutoFixSliceNames(true);
|
||||
pu.setThrowException(false);
|
||||
if (xverManager == null) {
|
||||
xverManager = new XVerExtensionManager(this);
|
||||
}
|
||||
pu.setXver(xverManager);
|
||||
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
|
||||
pu.sortDifferential(sd, p, p.getUrl(), errors, true);
|
||||
}
|
||||
pu.setDebug(false);
|
||||
for (String err : errors)
|
||||
msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
|
||||
pu.generateSnapshot(sd, p, p.getUrl(), Utilities.extractBaseUrl(sd.getUserString("path")), p.getName());
|
||||
for (ValidationMessage msg : msgs) {
|
||||
if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
|
||||
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
|
||||
}
|
||||
if (!p.hasSnapshot())
|
||||
throw new FHIRException(formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
|
||||
pu = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isIgnoreProfileErrors() {
|
||||
return ignoreProfileErrors;
|
||||
}
|
||||
|
||||
public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
|
||||
this.ignoreProfileErrors = ignoreProfileErrors;
|
||||
}
|
||||
|
||||
public String listMapUrls() {
|
||||
return Utilities.listCanonicalUrls(transforms.keys());
|
||||
}
|
||||
|
||||
public boolean isProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(boolean progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPackage(String id, String ver) {
|
||||
return loadedPackages.contains(id+"#"+ver);
|
||||
}
|
||||
|
||||
public boolean hasPackage(String idAndver) {
|
||||
return loadedPackages.contains(idAndver);
|
||||
}
|
||||
|
||||
public void setClock(TimeTracker tt) {
|
||||
clock = tt;
|
||||
}
|
||||
|
||||
public boolean isCanNoTS() {
|
||||
return canNoTS;
|
||||
}
|
||||
|
||||
public void setCanNoTS(boolean canNoTS) {
|
||||
this.canNoTS = canNoTS;
|
||||
}
|
||||
|
||||
public XVerExtensionManager getXVer() {
|
||||
if (xverManager == null) {
|
||||
xverManager = new XVerExtensionManager(this);
|
||||
}
|
||||
return xverManager;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
|
||||
}
|
||||
=======
|
||||
|
||||
}
|
||||
>>>>>>> 45530c0f5d95f642c9eaf68e48650d3a281d6736
|
|
@ -1,506 +0,0 @@
|
|||
package org.hl7.fhir.r4b.context;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext.ValidationResult;
|
||||
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r4b.formats.JsonParser;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r4b.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4b.model.Coding;
|
||||
import org.hl7.fhir.r4b.model.UriType;
|
||||
import org.hl7.fhir.r4b.model.ValueSet;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetFilterComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* This implements a two level cache.
|
||||
* - a temporary cache for remmbering previous local operations
|
||||
* - a persistent cache for rembering tx server operations
|
||||
*
|
||||
* the cache is a series of pairs: a map, and a list. the map is the loaded cache, the list is the persiistent cache, carefully maintained in order for version control consistency
|
||||
*
|
||||
* @author graha
|
||||
*
|
||||
*/
|
||||
public class TerminologyCache {
|
||||
public static final boolean TRANSIENT = false;
|
||||
public static final boolean PERMANENT = true;
|
||||
private static final String NAME_FOR_NO_SYSTEM = "all-systems";
|
||||
private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------";
|
||||
private static final String BREAK = "####";
|
||||
|
||||
public class CacheToken {
|
||||
private String name;
|
||||
private String key;
|
||||
private String request;
|
||||
public void setName(String n) {
|
||||
if (name == null)
|
||||
name = n;
|
||||
else if (!n.equals(name))
|
||||
name = NAME_FOR_NO_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
private class CacheEntry {
|
||||
private String request;
|
||||
private boolean persistent;
|
||||
private ValidationResult v;
|
||||
private ValueSetExpansionOutcome e;
|
||||
}
|
||||
|
||||
private class NamedCache {
|
||||
private String name;
|
||||
private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries
|
||||
private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
|
||||
}
|
||||
|
||||
|
||||
private Object lock;
|
||||
private String folder;
|
||||
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
|
||||
private static boolean noCaching;
|
||||
|
||||
// use lock from the context
|
||||
public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
|
||||
super();
|
||||
this.lock = lock;
|
||||
this.folder = folder;
|
||||
if (folder != null)
|
||||
load();
|
||||
}
|
||||
|
||||
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) {
|
||||
CacheToken ct = new CacheToken();
|
||||
if (code.hasSystem())
|
||||
ct.name = getNameForSystem(code.getSystem());
|
||||
else
|
||||
ct.name = NAME_FOR_NO_SYSTEM;
|
||||
// JsonParser json = new JsonParser();
|
||||
// json.setOutputStyle(OutputStyle.PRETTY);
|
||||
// ValueSet vsc = getVSEssense(vs);
|
||||
// try {
|
||||
// ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+"}";
|
||||
// } catch (IOException e) {
|
||||
// throw new Error(e);
|
||||
// }
|
||||
// ct.key = String.valueOf(hashNWS(ct.request));
|
||||
if (vs != null && vs.hasUrl()) {
|
||||
ct.key = vs.getUrl()+"#"+vs.getVersion();
|
||||
} else {
|
||||
ct.key = null;
|
||||
}
|
||||
return ct;
|
||||
}
|
||||
|
||||
public String extracted(JsonParser json, ValueSet vsc) throws IOException {
|
||||
String s = null;
|
||||
if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) {
|
||||
s = Integer.toString(vsc.hashCode()); // turn caching off - hack efficiency optimisation
|
||||
} else {
|
||||
s = json.composeString(vsc);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) {
|
||||
CacheToken ct = new CacheToken();
|
||||
for (Coding c : code.getCoding()) {
|
||||
if (c.hasSystem())
|
||||
ct.setName(getNameForSystem(c.getSystem()));
|
||||
}
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(OutputStyle.PRETTY);
|
||||
ValueSet vsc = getVSEssense(vs);
|
||||
try {
|
||||
ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+"}";
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
ct.key = String.valueOf(hashNWS(ct.request));
|
||||
return ct;
|
||||
}
|
||||
|
||||
public ValueSet getVSEssense(ValueSet vs) {
|
||||
if (vs == null)
|
||||
return null;
|
||||
ValueSet vsc = new ValueSet();
|
||||
vsc.setCompose(vs.getCompose());
|
||||
if (vs.hasExpansion()) {
|
||||
vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter());
|
||||
vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains());
|
||||
}
|
||||
return vsc;
|
||||
}
|
||||
|
||||
public CacheToken generateExpandToken(ValueSet vs, boolean heirarchical) {
|
||||
CacheToken ct = new CacheToken();
|
||||
ValueSet vsc = getVSEssense(vs);
|
||||
for (ConceptSetComponent inc : vs.getCompose().getInclude())
|
||||
if (inc.hasSystem())
|
||||
ct.setName(getNameForSystem(inc.getSystem()));
|
||||
for (ConceptSetComponent inc : vs.getCompose().getExclude())
|
||||
if (inc.hasSystem())
|
||||
ct.setName(getNameForSystem(inc.getSystem()));
|
||||
for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains())
|
||||
if (inc.hasSystem())
|
||||
ct.setName(getNameForSystem(inc.getSystem()));
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(OutputStyle.PRETTY);
|
||||
try {
|
||||
ct.request = "{\"hierarchical\" : "+(heirarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n";
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
ct.key = String.valueOf(hashNWS(ct.request));
|
||||
return ct;
|
||||
}
|
||||
|
||||
private String getNameForSystem(String system) {
|
||||
if (system.equals("http://snomed.info/sct"))
|
||||
return "snomed";
|
||||
if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
|
||||
return "rxnorm";
|
||||
if (system.equals("http://loinc.org"))
|
||||
return "loinc";
|
||||
if (system.equals("http://unitsofmeasure.org"))
|
||||
return "ucum";
|
||||
if (system.startsWith("http://hl7.org/fhir/sid/"))
|
||||
return system.substring(24).replace("/", "");
|
||||
if (system.startsWith("urn:iso:std:iso:"))
|
||||
return "iso"+system.substring(16).replace(":", "");
|
||||
if (system.startsWith("http://terminology.hl7.org/CodeSystem/"))
|
||||
return system.substring(38).replace("/", "");
|
||||
if (system.startsWith("http://hl7.org/fhir/"))
|
||||
return system.substring(20).replace("/", "");
|
||||
if (system.equals("urn:ietf:bcp:47"))
|
||||
return "lang";
|
||||
if (system.equals("urn:ietf:bcp:13"))
|
||||
return "mimetypes";
|
||||
if (system.equals("urn:iso:std:iso:11073:10101"))
|
||||
return "11073";
|
||||
if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
|
||||
return "dicom";
|
||||
return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X");
|
||||
}
|
||||
|
||||
public NamedCache getNamedCache(CacheToken cacheToken) {
|
||||
NamedCache nc = caches.get(cacheToken.name);
|
||||
if (nc == null) {
|
||||
nc = new NamedCache();
|
||||
nc.name = cacheToken.name;
|
||||
caches.put(nc.name, nc);
|
||||
}
|
||||
return nc;
|
||||
}
|
||||
|
||||
public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) {
|
||||
synchronized (lock) {
|
||||
NamedCache nc = getNamedCache(cacheToken);
|
||||
CacheEntry e = nc.map.get(cacheToken.key);
|
||||
if (e == null)
|
||||
return null;
|
||||
else
|
||||
return e.e;
|
||||
}
|
||||
}
|
||||
|
||||
public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) {
|
||||
synchronized (lock) {
|
||||
NamedCache nc = getNamedCache(cacheToken);
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.request = cacheToken.request;
|
||||
e.persistent = persistent;
|
||||
e.e = res;
|
||||
store(cacheToken, persistent, nc, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) {
|
||||
if (noCaching) {
|
||||
return;
|
||||
}
|
||||
boolean n = nc.map.containsKey(cacheToken.key);
|
||||
nc.map.put(cacheToken.key, e);
|
||||
if (persistent) {
|
||||
if (n) {
|
||||
for (int i = nc.list.size()- 1; i>= 0; i--) {
|
||||
if (nc.list.get(i).request.equals(e.request)) {
|
||||
nc.list.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
nc.list.add(e);
|
||||
save(nc);
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationResult getValidation(CacheToken cacheToken) {
|
||||
if (cacheToken.key == null) {
|
||||
return null;
|
||||
}
|
||||
synchronized (lock) {
|
||||
NamedCache nc = getNamedCache(cacheToken);
|
||||
CacheEntry e = nc.map.get(cacheToken.key);
|
||||
if (e == null)
|
||||
return null;
|
||||
else
|
||||
return e.v;
|
||||
}
|
||||
}
|
||||
|
||||
public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) {
|
||||
if (cacheToken.key != null) {
|
||||
synchronized (lock) {
|
||||
NamedCache nc = getNamedCache(cacheToken);
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.request = cacheToken.request;
|
||||
e.persistent = persistent;
|
||||
e.v = res;
|
||||
store(cacheToken, persistent, nc, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// persistence
|
||||
|
||||
public void save() {
|
||||
|
||||
}
|
||||
|
||||
private void save(NamedCache nc) {
|
||||
if (folder == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, nc.name+".cache")), "UTF-8");
|
||||
sw.write(ENTRY_MARKER+"\r\n");
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(OutputStyle.PRETTY);
|
||||
for (CacheEntry ce : nc.list) {
|
||||
sw.write(ce.request.trim());
|
||||
sw.write(BREAK+"\r\n");
|
||||
if (ce.e != null) {
|
||||
sw.write("e: {\r\n");
|
||||
if (ce.e.getValueset() != null)
|
||||
sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n");
|
||||
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n");
|
||||
} else {
|
||||
sw.write("v: {\r\n");
|
||||
sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\",\r\n");
|
||||
sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\",\r\n");
|
||||
sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\",\r\n");
|
||||
sw.write(" \"severity\" : "+(ce.v.getSeverity() == null ? "null" : "\""+ce.v.getSeverity().toCode().trim()+"\"")+",\r\n");
|
||||
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\"\r\n}\r\n");
|
||||
}
|
||||
sw.write(ENTRY_MARKER+"\r\n");
|
||||
}
|
||||
sw.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("error saving "+nc.name+": "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void load() throws FHIRException {
|
||||
for (String fn : new File(folder).list()) {
|
||||
if (fn.endsWith(".cache") && !fn.equals("validation.cache")) {
|
||||
try {
|
||||
String title = fn.substring(0, fn.lastIndexOf("."));
|
||||
NamedCache nc = new NamedCache();
|
||||
nc.name = title;
|
||||
caches.put(title, nc);
|
||||
String src = TextFile.fileToString(Utilities.path(folder, fn));
|
||||
if (src.startsWith("?"))
|
||||
src = src.substring(1);
|
||||
int i = src.indexOf(ENTRY_MARKER);
|
||||
while (i > -1) {
|
||||
String s = src.substring(0, i);
|
||||
src = src.substring(i+ENTRY_MARKER.length()+1);
|
||||
i = src.indexOf(ENTRY_MARKER);
|
||||
if (!Utilities.noString(s)) {
|
||||
int j = s.indexOf(BREAK);
|
||||
String q = s.substring(0, j);
|
||||
String p = s.substring(j+BREAK.length()+1).trim();
|
||||
CacheEntry ce = new CacheEntry();
|
||||
ce.persistent = true;
|
||||
ce.request = q;
|
||||
boolean e = p.charAt(0) == 'e';
|
||||
p = p.substring(3);
|
||||
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(p);
|
||||
String error = loadJS(o.get("error"));
|
||||
if (e) {
|
||||
if (o.has("valueSet"))
|
||||
ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN);
|
||||
else
|
||||
ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN);
|
||||
} else {
|
||||
IssueSeverity severity = o.get("severity") instanceof JsonNull ? null : IssueSeverity.fromCode(o.get("severity").getAsString());
|
||||
String display = loadJS(o.get("display"));
|
||||
String code = loadJS(o.get("code"));
|
||||
String system = loadJS(o.get("system"));
|
||||
ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setCode(code));
|
||||
}
|
||||
nc.map.put(String.valueOf(hashNWS(ce.request)), ce);
|
||||
nc.list.add(ce);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error loading "+fn+": "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String loadJS(JsonElement e) {
|
||||
if (e == null)
|
||||
return null;
|
||||
if (!(e instanceof JsonPrimitive))
|
||||
return null;
|
||||
String s = e.getAsString();
|
||||
if ("".equals(s))
|
||||
return null;
|
||||
return s;
|
||||
}
|
||||
|
||||
private String hashNWS(String s) {
|
||||
s = StringUtils.remove(s, ' ');
|
||||
s = StringUtils.remove(s, '\n');
|
||||
s = StringUtils.remove(s, '\r');
|
||||
return String.valueOf(s.hashCode());
|
||||
}
|
||||
|
||||
// management
|
||||
|
||||
public TerminologyCache copy() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public String summary(ValueSet vs) {
|
||||
if (vs == null)
|
||||
return "null";
|
||||
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (ConceptSetComponent cc : vs.getCompose().getInclude())
|
||||
b.append("Include "+getIncSummary(cc));
|
||||
for (ConceptSetComponent cc : vs.getCompose().getExclude())
|
||||
b.append("Exclude "+getIncSummary(cc));
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String getIncSummary(ConceptSetComponent cc) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (UriType vs : cc.getValueSet())
|
||||
b.append(vs.asStringValue());
|
||||
String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : "";
|
||||
String system = cc.getSystem();
|
||||
if (cc.hasConcept())
|
||||
return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd;
|
||||
if (cc.hasFilter()) {
|
||||
String s = "";
|
||||
for (ConceptSetFilterComponent f : cc.getFilter()) {
|
||||
if (!Utilities.noString(s))
|
||||
s = s + " & ";
|
||||
s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue();
|
||||
}
|
||||
return "from "+system+" where "+s+vsd;
|
||||
}
|
||||
return "All codes from "+system+vsd;
|
||||
}
|
||||
|
||||
public String summary(Coding code) {
|
||||
return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\"";
|
||||
}
|
||||
|
||||
public String summary(CodeableConcept code) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{");
|
||||
boolean first = true;
|
||||
for (Coding c : code.getCoding()) {
|
||||
if (first) first = false; else b.append(",");
|
||||
b.append(summary(c));
|
||||
}
|
||||
b.append("}: \"");
|
||||
b.append(code.getText());
|
||||
b.append("\"");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public static boolean isNoCaching() {
|
||||
return noCaching;
|
||||
}
|
||||
|
||||
public static void setNoCaching(boolean noCaching) {
|
||||
TerminologyCache.noCaching = noCaching;
|
||||
}
|
||||
|
||||
public void removeCS(String url) {
|
||||
synchronized (lock) {
|
||||
String name = getNameForSystem(url);
|
||||
if (caches.containsKey(name)) {
|
||||
caches.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,978 +0,0 @@
|
|||
package org.hl7.fhir.r4b.elementmodel;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
|
||||
import org.hl7.fhir.r4b.model.Base;
|
||||
import org.hl7.fhir.r4b.model.DataType;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r4b.model.Enumerations.BindingStrength;
|
||||
import org.hl7.fhir.r4b.model.ICoding;
|
||||
import org.hl7.fhir.r4b.model.StringType;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.model.TypeConvertor;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.utilities.ElementDecoration;
|
||||
import org.hl7.fhir.utilities.ElementDecoration.DecorationType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
||||
/**
|
||||
* This class represents the underlying reference model of FHIR
|
||||
*
|
||||
* A resource is nothing but a set of elements, where every element has a
|
||||
* name, maybe a stated type, maybe an id, and either a value or child elements
|
||||
* (one or the other, but not both or neither)
|
||||
*
|
||||
* @author Grahame Grieve
|
||||
*
|
||||
*/
|
||||
public class Element extends Base {
|
||||
|
||||
|
||||
public enum SpecialElement {
|
||||
CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
|
||||
|
||||
public static SpecialElement fromProperty(Property property) {
|
||||
if (property.getStructure().getType().equals("Parameters"))
|
||||
return PARAMETER;
|
||||
if (property.getStructure().getType().equals("Bundle") && property.getName().equals("resource"))
|
||||
return BUNDLE_ENTRY;
|
||||
if (property.getStructure().getType().equals("Bundle") && property.getName().equals("outcome"))
|
||||
return BUNDLE_OUTCOME;
|
||||
if (property.getName().equals("contained"))
|
||||
return CONTAINED;
|
||||
throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> comments;// not relevant for production, but useful in documentation
|
||||
private String name;
|
||||
private String type;
|
||||
private String value;
|
||||
private int index = -1;
|
||||
private List<Element> children;
|
||||
private Property property;
|
||||
private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places
|
||||
private int line;
|
||||
private int col;
|
||||
private SpecialElement special;
|
||||
private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
|
||||
private String explicitType; // for xsi:type attribute
|
||||
private Element parentForValidator;
|
||||
private boolean hasParentForValidator;
|
||||
private String path;
|
||||
private List<ValidationMessage> messages;
|
||||
|
||||
public Element(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Element(Element other) {
|
||||
super();
|
||||
name = other.name;
|
||||
type = other.type;
|
||||
property = other.property;
|
||||
elementProperty = other.elementProperty;
|
||||
special = other.special;
|
||||
}
|
||||
|
||||
public Element(String name, Property property) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public Element(String name, Property property, String type, String value) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.property = property;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
|
||||
this.property = property;
|
||||
this.elementProperty = elementProperty;
|
||||
this.special = special;
|
||||
}
|
||||
|
||||
public SpecialElement getSpecial() {
|
||||
return special;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
if (type == null)
|
||||
return property.getType(name);
|
||||
else
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean parseSingle() {
|
||||
return !(children == null || children.isEmpty());
|
||||
}
|
||||
|
||||
public List<Element> getChildren() {
|
||||
if (children == null)
|
||||
children = new ArrayList<Element>();
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean hasComments() {
|
||||
return !(comments == null || comments.isEmpty());
|
||||
}
|
||||
|
||||
public List<String> getComments() {
|
||||
if (comments == null)
|
||||
comments = new ArrayList<String>();
|
||||
return comments;
|
||||
}
|
||||
|
||||
public Property getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public List<Element> getChildrenByName(String name) {
|
||||
List<Element> res = new ArrayList<Element>();
|
||||
if (hasChildren()) {
|
||||
for (Element child : children)
|
||||
if (name.equals(child.getName()))
|
||||
res.add(child);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public void numberChildren() {
|
||||
if (children == null)
|
||||
return;
|
||||
|
||||
String last = "";
|
||||
int index = 0;
|
||||
for (Element child : children) {
|
||||
if (child.getProperty().isList()) {
|
||||
if (last.equals(child.getName())) {
|
||||
index++;
|
||||
} else {
|
||||
last = child.getName();
|
||||
index = 0;
|
||||
}
|
||||
child.index = index;
|
||||
} else {
|
||||
child.index = -1;
|
||||
}
|
||||
child.numberChildren();
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public boolean hasIndex() {
|
||||
return index > -1;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public String getChildValue(String name) {
|
||||
if (children == null)
|
||||
return null;
|
||||
for (Element child : children) {
|
||||
if (name.equals(child.getName()))
|
||||
return child.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setChildValue(String name, String value) {
|
||||
if (children == null)
|
||||
children = new ArrayList<Element>();
|
||||
for (Element child : children) {
|
||||
if (name.equals(child.getName())) {
|
||||
if (!child.isPrimitive())
|
||||
throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
|
||||
child.setValue(value);
|
||||
}
|
||||
}
|
||||
try {
|
||||
setProperty(name.hashCode(), name, new StringType(value));
|
||||
} catch (FHIRException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Element> getChildren(String name) {
|
||||
List<Element> res = new ArrayList<Element>();
|
||||
if (children != null)
|
||||
for (Element child : children) {
|
||||
if (name.equals(child.getName()))
|
||||
res.add(child);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public boolean hasType() {
|
||||
if (type == null)
|
||||
return property.hasType(name);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
|
||||
if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
|
||||
// String tn = getType();
|
||||
// throw new Error(tn+" not done yet");
|
||||
Base[] b = new Base[1];
|
||||
b[0] = new StringType(value);
|
||||
return b;
|
||||
}
|
||||
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (child.getName().equals(name))
|
||||
result.add(child);
|
||||
if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]"))
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty() && checkValid) {
|
||||
// throw new FHIRException("not determined yet");
|
||||
}
|
||||
return result.toArray(new Base[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listChildren(List<org.hl7.fhir.r4b.model.Property> childProps) {
|
||||
if (children != null) {
|
||||
Map<String, org.hl7.fhir.r4b.model.Property> map = new HashMap<String, org.hl7.fhir.r4b.model.Property>();
|
||||
for (Element c : children) {
|
||||
org.hl7.fhir.r4b.model.Property p = map.get(c.getName());
|
||||
if (p == null) {
|
||||
p = new org.hl7.fhir.r4b.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c);
|
||||
childProps.add(p);
|
||||
map.put(c.getName(), p);
|
||||
|
||||
} else
|
||||
p.getValues().add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base setProperty(int hash, String name, Base value) throws FHIRException {
|
||||
if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
|
||||
this.xhtml = TypeConvertor.castToXhtml(value);
|
||||
this.value = TypeConvertor.castToXhtmlString(value);
|
||||
return this;
|
||||
}
|
||||
if (isPrimitive() && (hash == "value".hashCode())) {
|
||||
this.value = TypeConvertor.castToString(value).asStringValue();
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!value.isPrimitive() && !(value instanceof Element)) {
|
||||
if (isDataType(value))
|
||||
value = convertToElement(property.getChild(name), value);
|
||||
else
|
||||
throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
|
||||
}
|
||||
|
||||
if (children == null)
|
||||
children = new ArrayList<Element>();
|
||||
Element childForValue = null;
|
||||
|
||||
// look through existing children
|
||||
for (Element child : children) {
|
||||
if (child.getName().equals(name)) {
|
||||
if (!child.isList()) {
|
||||
childForValue = child;
|
||||
break;
|
||||
} else {
|
||||
Element ne = new Element(child);
|
||||
children.add(ne);
|
||||
numberChildren();
|
||||
childForValue = ne;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
if (childForValue == null)
|
||||
for (Property p : property.getChildProperties(this.name, type)) {
|
||||
int t = -1;
|
||||
for (int c =0; c < children.size(); c++) {
|
||||
Element e = children.get(c);
|
||||
if (p.getName().equals(e.getName()))
|
||||
t = c;
|
||||
}
|
||||
if (t >= i)
|
||||
i = t+1;
|
||||
if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
|
||||
Element ne = new Element(name, p);
|
||||
children.add(i, ne);
|
||||
childForValue = ne;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (childForValue == null)
|
||||
throw new Error("Cannot set property "+name+" on "+this.name);
|
||||
else if (value.isPrimitive()) {
|
||||
if (childForValue.property.getName().endsWith("[x]"))
|
||||
childForValue.name = name+Utilities.capitalize(value.fhirType());
|
||||
childForValue.setValue(value.primitiveValue());
|
||||
} else {
|
||||
Element ve = (Element) value;
|
||||
childForValue.type = ve.getType();
|
||||
if (childForValue.property.getName().endsWith("[x]"))
|
||||
childForValue.name = name+Utilities.capitalize(childForValue.type);
|
||||
else if (value.isResource()) {
|
||||
if (childForValue.elementProperty == null)
|
||||
childForValue.elementProperty = childForValue.property;
|
||||
childForValue.property = ve.property;
|
||||
childForValue.special = SpecialElement.BUNDLE_ENTRY;
|
||||
}
|
||||
if (ve.children != null) {
|
||||
if (childForValue.children == null)
|
||||
childForValue.children = new ArrayList<Element>();
|
||||
else
|
||||
childForValue.children.clear();
|
||||
childForValue.children.addAll(ve.children);
|
||||
}
|
||||
}
|
||||
return childForValue;
|
||||
}
|
||||
|
||||
private Base convertToElement(Property prop, Base v) throws FHIRException {
|
||||
return new ObjectConverter(property.getContext()).convert(prop, (DataType) v);
|
||||
}
|
||||
|
||||
private boolean isDataType(Base v) {
|
||||
return v instanceof DataType && property.getContext().getTypeNames().contains(v.fhirType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base makeProperty(int hash, String name) throws FHIRException {
|
||||
if (isPrimitive() && (hash == "value".hashCode())) {
|
||||
return new StringType(value);
|
||||
}
|
||||
|
||||
if (children == null)
|
||||
children = new ArrayList<Element>();
|
||||
|
||||
// look through existing children
|
||||
for (Element child : children) {
|
||||
if (child.getName().equals(name)) {
|
||||
if (!child.isList()) {
|
||||
return child;
|
||||
} else {
|
||||
Element ne = new Element(child);
|
||||
children.add(ne);
|
||||
numberChildren();
|
||||
return ne;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Property p : property.getChildProperties(this.name, type)) {
|
||||
if (p.getName().equals(name)) {
|
||||
Element ne = new Element(name, p);
|
||||
children.add(ne);
|
||||
return ne;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unrecognised name "+name+" on "+this.name);
|
||||
}
|
||||
|
||||
private int maxToInt(String max) {
|
||||
if (max.equals("*"))
|
||||
return Integer.MAX_VALUE;
|
||||
else
|
||||
return Integer.parseInt(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitive() {
|
||||
return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBooleanPrimitive() {
|
||||
return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResource() {
|
||||
return property.isResource();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasPrimitiveValue() {
|
||||
return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String primitiveValue() {
|
||||
if (isPrimitive())
|
||||
return value;
|
||||
else {
|
||||
if (hasPrimitiveValue() && children != null) {
|
||||
for (Element c : children) {
|
||||
if (c.getName().equals("value"))
|
||||
return c.primitiveValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// for the validator
|
||||
public int line() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int col() {
|
||||
return col;
|
||||
}
|
||||
|
||||
public Element markLocation(int line, int col) {
|
||||
this.line = line;
|
||||
this.col = col;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clearDecorations() {
|
||||
clearUserData("fhir.decorations");
|
||||
for (Element e : children)
|
||||
e.clearDecorations();
|
||||
}
|
||||
|
||||
public void markValidation(StructureDefinition profile, ElementDefinition definition) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations");
|
||||
if (decorations == null) {
|
||||
decorations = new ArrayList<>();
|
||||
setUserData("fhir.decorations", decorations);
|
||||
}
|
||||
decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getUserString("path"), definition.getPath()));
|
||||
if (definition.getId() != null && tail(definition.getId()).contains(":")) {
|
||||
String[] details = tail(definition.getId()).split(":");
|
||||
decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private String tail(String id) {
|
||||
return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id;
|
||||
}
|
||||
|
||||
public Element getNamedChild(String name) {
|
||||
if (children == null)
|
||||
return null;
|
||||
Element result = null;
|
||||
for (Element child : children) {
|
||||
if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) {
|
||||
if (child.getName().equals(name) || (child.getName().length() > child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) {
|
||||
if (result == null)
|
||||
result = child;
|
||||
else
|
||||
throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void getNamedChildren(String name, List<Element> list) {
|
||||
if (children != null)
|
||||
for (Element child : children)
|
||||
if (child.getName().equals(name))
|
||||
list.add(child);
|
||||
}
|
||||
|
||||
public String getNamedChildValue(String name) {
|
||||
Element child = getNamedChild(name);
|
||||
return child == null ? null : child.value;
|
||||
}
|
||||
|
||||
public void getNamedChildrenWithWildcard(String string, List<Element> values) {
|
||||
Validate.isTrue(string.endsWith("[x]"));
|
||||
|
||||
String start = string.substring(0, string.length() - 3);
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (child.getName().startsWith(start)) {
|
||||
values.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public XhtmlNode getXhtml() {
|
||||
return xhtml;
|
||||
}
|
||||
|
||||
public Element setXhtml(XhtmlNode xhtml) {
|
||||
this.xhtml = xhtml;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
// GG: this used to also test !"".equals(value).
|
||||
// the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath.
|
||||
// it should not cause any problems in real life.
|
||||
if (value != null) {
|
||||
return false;
|
||||
}
|
||||
for (Element next : getChildren()) {
|
||||
if (!next.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Property getElementProperty() {
|
||||
return elementProperty;
|
||||
}
|
||||
|
||||
public boolean hasElementProperty() {
|
||||
return elementProperty != null;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return getNamedChild(name) != null;
|
||||
}
|
||||
|
||||
public boolean hasChildren(String name) {
|
||||
if (children != null)
|
||||
for (Element child : children)
|
||||
if (child.getName().equals(name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
return getChildValue("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
setChildValue("id", value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equalsDeep(Base other) {
|
||||
if (!super.equalsDeep(other))
|
||||
return false;
|
||||
if (isPrimitive() && other.isPrimitive())
|
||||
return primitiveValue().equals(other.primitiveValue());
|
||||
if (isPrimitive() || other.isPrimitive())
|
||||
return false;
|
||||
Set<String> processed = new HashSet<String>();
|
||||
for (org.hl7.fhir.r4b.model.Property p : children()) {
|
||||
String name = p.getName();
|
||||
processed.add(name);
|
||||
org.hl7.fhir.r4b.model.Property o = other.getChildByName(name);
|
||||
if (!equalsDeep(p, o))
|
||||
return false;
|
||||
}
|
||||
for (org.hl7.fhir.r4b.model.Property p : children()) {
|
||||
String name = p.getName();
|
||||
if (!processed.contains(name)) {
|
||||
org.hl7.fhir.r4b.model.Property o = other.getChildByName(name);
|
||||
if (!equalsDeep(p, o))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean equalsDeep(org.hl7.fhir.r4b.model.Property p, org.hl7.fhir.r4b.model.Property o) {
|
||||
if (o == null || p == null)
|
||||
return false;
|
||||
if (p.getValues().size() != o.getValues().size())
|
||||
return false;
|
||||
for (int i = 0; i < p.getValues().size(); i++)
|
||||
if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsShallow(Base other) {
|
||||
if (!super.equalsShallow(other))
|
||||
return false;
|
||||
if (isPrimitive() && other.isPrimitive())
|
||||
return primitiveValue().equals(other.primitiveValue());
|
||||
if (isPrimitive() || other.isPrimitive())
|
||||
return false;
|
||||
return true; //?
|
||||
}
|
||||
|
||||
public DataType asType() throws FHIRException {
|
||||
return new ObjectConverter(property.getContext()).convertToType(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMetadataBased() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isList() {
|
||||
if (elementProperty != null)
|
||||
return elementProperty.isList();
|
||||
else
|
||||
return property.isList();
|
||||
}
|
||||
|
||||
public boolean isBaseList() {
|
||||
if (elementProperty != null)
|
||||
return elementProperty.isBaseList();
|
||||
else
|
||||
return property.isBaseList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
|
||||
Property p = property.getChildSimpleName(this.name, name);
|
||||
if (p != null) {
|
||||
Set<String> types = new HashSet<String>();
|
||||
for (TypeRefComponent tr : p.getDefinition().getType()) {
|
||||
types.add(tr.getCode());
|
||||
}
|
||||
return types.toArray(new String[]{});
|
||||
}
|
||||
return super.getTypesForProperty(hash, name);
|
||||
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
if (children != null) {
|
||||
List<Element> remove = new ArrayList<Element>();
|
||||
for (Element child : children) {
|
||||
child.sort();
|
||||
if (child.isEmpty())
|
||||
remove.add(child);
|
||||
}
|
||||
children.removeAll(remove);
|
||||
Collections.sort(children, new ElementSortComparator(this, this.property));
|
||||
}
|
||||
}
|
||||
|
||||
public class ElementSortComparator implements Comparator<Element> {
|
||||
private List<ElementDefinition> children;
|
||||
public ElementSortComparator(Element e, Property property) {
|
||||
String tn = e.getType();
|
||||
StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, property.getContext().getOverrideVersionNs()));
|
||||
if (sd != null && !sd.getAbstract())
|
||||
children = sd.getSnapshot().getElement();
|
||||
else
|
||||
children = property.getStructure().getSnapshot().getElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Element e0, Element e1) {
|
||||
int i0 = find(e0);
|
||||
int i1 = find(e1);
|
||||
return Integer.compare(i0, i1);
|
||||
}
|
||||
private int find(Element e0) {
|
||||
int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition());
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ICodingImpl implements ICoding {
|
||||
private String system;
|
||||
private String version;
|
||||
private String code;
|
||||
private String display;
|
||||
private boolean doesSystem;
|
||||
private boolean doesVersion;
|
||||
private boolean doesCode;
|
||||
private boolean doesDisplay;
|
||||
public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) {
|
||||
super();
|
||||
this.doesCode = doesCode;
|
||||
this.doesSystem = doesSystem;
|
||||
this.doesVersion = doesVersion;
|
||||
this.doesDisplay = doesDisplay;
|
||||
}
|
||||
public String getSystem() {
|
||||
return system;
|
||||
}
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
public boolean hasSystem() {
|
||||
return !Utilities.noString(system);
|
||||
}
|
||||
public boolean hasVersion() {
|
||||
return !Utilities.noString(version);
|
||||
}
|
||||
public boolean hasCode() {
|
||||
return !Utilities.noString(code);
|
||||
}
|
||||
public boolean hasDisplay() {
|
||||
return !Utilities.noString(display);
|
||||
}
|
||||
public boolean supportsSystem() {
|
||||
return doesSystem;
|
||||
}
|
||||
public boolean supportsVersion() {
|
||||
return doesVersion;
|
||||
}
|
||||
public boolean supportsCode() {
|
||||
return doesCode;
|
||||
}
|
||||
public boolean supportsDisplay() {
|
||||
return doesDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
public ICoding getAsICoding() throws FHIRException {
|
||||
if ("code".equals(fhirType())) {
|
||||
if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED)
|
||||
return null;
|
||||
ICodingImpl c = new ICodingImpl(true, true, false, false);
|
||||
c.code = primitiveValue();
|
||||
ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getDefinition().getBinding(), true, false);
|
||||
if (vse.getValueset() == null)
|
||||
return null;
|
||||
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
|
||||
if (cc.getCode().equals(c.code)) {
|
||||
c.system = cc.getSystem();
|
||||
if (cc.hasVersion()) {
|
||||
c.doesVersion = true;
|
||||
c.version = cc.getVersion();
|
||||
}
|
||||
if (cc.hasDisplay()) {
|
||||
c.doesDisplay = true;
|
||||
c.display = cc.getDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.system == null)
|
||||
return null;
|
||||
return c;
|
||||
} else if ("Coding".equals(fhirType())) {
|
||||
ICodingImpl c = new ICodingImpl(true, true, true, true);
|
||||
c.system = getNamedChildValue("system");
|
||||
c.code = getNamedChildValue("code");
|
||||
c.display = getNamedChildValue("display");
|
||||
c.version = getNamedChildValue("version");
|
||||
return c;
|
||||
} else if ("Quantity".equals(fhirType())) {
|
||||
ICodingImpl c = new ICodingImpl(true, true, false, false);
|
||||
c.system = getNamedChildValue("system");
|
||||
c.code = getNamedChildValue("code");
|
||||
return c;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getExplicitType() {
|
||||
return explicitType;
|
||||
}
|
||||
|
||||
public void setExplicitType(String explicitType) {
|
||||
this.explicitType = explicitType;
|
||||
}
|
||||
|
||||
public boolean hasDescendant(Element element) {
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (element == child || child.hasDescendant(element)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Element getExtension(String url) {
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
|
||||
String u = child.getChildValue("url");
|
||||
if (url.equals(u)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Base getExtensionValue(String url) {
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
|
||||
String u = child.getChildValue("url");
|
||||
if (url.equals(u)) {
|
||||
return child.getNamedChild("value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasExtension(String url) {
|
||||
if (children != null) {
|
||||
for (Element child : children) {
|
||||
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
|
||||
String u = child.getChildValue("url");
|
||||
if (url.equals(u)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* this is set by the instance validator. There's no reason to maintain this when working with an element tree, and so it should be ignored outside the validator
|
||||
*/
|
||||
public Element getParentForValidator() {
|
||||
if (!hasParentForValidator) {
|
||||
throw new Error("Parent not set");
|
||||
}
|
||||
return parentForValidator;
|
||||
}
|
||||
|
||||
public void setParentForValidator(Element parentForValidator) {
|
||||
this.parentForValidator = parentForValidator;
|
||||
this.hasParentForValidator = true;
|
||||
}
|
||||
|
||||
public boolean hasParentForValidator() {
|
||||
return hasParentForValidator;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
comments = null;
|
||||
children.clear();;
|
||||
property = null;
|
||||
elementProperty = null;
|
||||
xhtml = null;
|
||||
path = null;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void addMessage(ValidationMessage vm) {
|
||||
if (messages == null) {
|
||||
messages = new ArrayList<>();
|
||||
}
|
||||
messages.add(vm);
|
||||
}
|
||||
|
||||
public boolean hasMessages() {
|
||||
return messages != null && !messages.isEmpty();
|
||||
}
|
||||
|
||||
public List<ValidationMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package org.hl7.fhir.r4b.elementmodel;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext;
|
||||
import org.hl7.fhir.r4b.formats.FormatUtilities;
|
||||
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
|
||||
import org.hl7.fhir.r4b.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
|
||||
public abstract class ParserBase {
|
||||
|
||||
public interface ILinkResolver {
|
||||
String resolveType(String type);
|
||||
String resolveProperty(Property property);
|
||||
String resolvePage(String string);
|
||||
}
|
||||
|
||||
public enum ValidationPolicy { NONE, QUICK, EVERYTHING }
|
||||
|
||||
public boolean isPrimitive(String code) {
|
||||
return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml", "url", "canonical");
|
||||
|
||||
// StructureDefinition sd = context.fetchTypeDefinition(code);
|
||||
// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
|
||||
}
|
||||
|
||||
protected IWorkerContext context;
|
||||
protected ValidationPolicy policy;
|
||||
protected List<ValidationMessage> errors;
|
||||
protected ILinkResolver linkResolver;
|
||||
protected boolean showDecorations;
|
||||
|
||||
public ParserBase(IWorkerContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
policy = ValidationPolicy.NONE;
|
||||
}
|
||||
|
||||
public void setupValidation(ValidationPolicy policy, List<ValidationMessage> errors) {
|
||||
this.policy = policy;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public abstract Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
|
||||
|
||||
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException;
|
||||
|
||||
//FIXME: i18n should be done here
|
||||
public void logError(int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
|
||||
if (policy == ValidationPolicy.EVERYTHING) {
|
||||
ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
|
||||
errors.add(msg);
|
||||
} else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
|
||||
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
|
||||
}
|
||||
|
||||
|
||||
protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError {
|
||||
if (ns == null) {
|
||||
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS__CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAMESPACE, name), IssueSeverity.FATAL);
|
||||
return null;
|
||||
}
|
||||
if (name == null) {
|
||||
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
|
||||
return null;
|
||||
}
|
||||
for (StructureDefinition sd : context.allStructures()) {
|
||||
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) {
|
||||
if(name.equals(sd.getType()) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
|
||||
return sd;
|
||||
String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
|
||||
if ((name.equals(sd.getType()) || name.equals(tail(sd.getType())) ) && ns != null && ns.equals(sns))
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, ns, name), IssueSeverity.FATAL);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String tail(String type) {
|
||||
return type.contains("/") ? type.substring(type.lastIndexOf("/")+1) : type;
|
||||
}
|
||||
|
||||
protected StructureDefinition getDefinition(int line, int col, String name) throws FHIRFormatError {
|
||||
if (name == null) {
|
||||
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
|
||||
return null;
|
||||
}
|
||||
// first pass: only look at base definitions
|
||||
for (StructureDefinition sd : context.getStructures()) {
|
||||
if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) {
|
||||
context.generateSnapshot(sd);
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
for (StructureDefinition sd : context.getStructures()) {
|
||||
if (name.equals(sd.getType()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
|
||||
context.generateSnapshot(sd);
|
||||
return sd;
|
||||
}
|
||||
}
|
||||
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
|
||||
return null;
|
||||
}
|
||||
|
||||
public ILinkResolver getLinkResolver() {
|
||||
return linkResolver;
|
||||
}
|
||||
|
||||
public ParserBase setLinkResolver(ILinkResolver linkResolver) {
|
||||
this.linkResolver = linkResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isShowDecorations() {
|
||||
return showDecorations;
|
||||
}
|
||||
|
||||
public void setShowDecorations(boolean showDecorations) {
|
||||
this.showDecorations = showDecorations;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -47,13 +47,14 @@ public class ExpressionNode {
|
|||
Custom,
|
||||
|
||||
Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single,
|
||||
First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, ReplaceMatches, Contains, Replace, Length,
|
||||
First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches, Contains, Replace, Length,
|
||||
Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue,
|
||||
HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo,
|
||||
Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, Power, Truncate,
|
||||
|
||||
// R3 functions
|
||||
Encode, Decode, Escape, Unescape, Trim, Split, Join,
|
||||
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
|
||||
|
||||
// Local extensions to FHIRPath
|
||||
HtmlChecks1, HtmlChecks2, AliasAs, Alias;
|
||||
|
||||
|
@ -93,6 +94,7 @@ public class ExpressionNode {
|
|||
if (name.equals("startsWith")) return Function.StartsWith;
|
||||
if (name.equals("endsWith")) return Function.EndsWith;
|
||||
if (name.equals("matches")) return Function.Matches;
|
||||
if (name.equals("matchesFull")) return Function.MatchesFull;
|
||||
if (name.equals("replaceMatches")) return Function.ReplaceMatches;
|
||||
if (name.equals("contains")) return Function.Contains;
|
||||
if (name.equals("replace")) return Function.Replace;
|
||||
|
@ -151,6 +153,10 @@ public class ExpressionNode {
|
|||
if (name.equals("log")) return Function.Log;
|
||||
if (name.equals("power")) return Function.Power;
|
||||
if (name.equals("truncate")) return Function.Truncate;
|
||||
if (name.equals("lowBoundary")) return Function.LowBoundary;
|
||||
if (name.equals("highBoundary")) return Function.HighBoundary;
|
||||
if (name.equals("precision")) return Function.Precision;
|
||||
|
||||
return null;
|
||||
}
|
||||
public String toCode() {
|
||||
|
@ -190,6 +196,7 @@ public class ExpressionNode {
|
|||
case StartsWith : return "startsWith";
|
||||
case EndsWith : return "endsWith";
|
||||
case Matches : return "matches";
|
||||
case MatchesFull : return "matchesFull";
|
||||
case ReplaceMatches : return "replaceMatches";
|
||||
case Contains : return "contains";
|
||||
case Replace : return "replace";
|
||||
|
@ -247,7 +254,9 @@ public class ExpressionNode {
|
|||
case Log : return "log";
|
||||
case Power : return "power";
|
||||
case Truncate: return "truncate";
|
||||
|
||||
case LowBoundary: return "lowBoundary";
|
||||
case HighBoundary: return "highBoundary";
|
||||
case Precision: return "precision";
|
||||
default: return "?custom?";
|
||||
}
|
||||
}
|
||||
|
@ -614,6 +623,9 @@ public class ExpressionNode {
|
|||
|
||||
public String check() {
|
||||
|
||||
if (kind == null) {
|
||||
return "Error in expression - node has no kind";
|
||||
}
|
||||
switch (kind) {
|
||||
case Name:
|
||||
if (Utilities.noString(name))
|
||||
|
|
|
@ -144,4 +144,9 @@ public class TimeType extends PrimitiveType<String> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fpValue() {
|
||||
return "@T"+primitiveValue();
|
||||
}
|
||||
|
||||
}
|
|
@ -298,7 +298,7 @@ public class TypeDetails {
|
|||
String t = ProfiledType.ns(n);
|
||||
if (typesContains(t))
|
||||
return true;
|
||||
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
|
||||
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
|
||||
t = FP_NS+Utilities.capitalize(n);
|
||||
if (typesContains(t))
|
||||
return true;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,354 +0,0 @@
|
|||
package org.hl7.fhir.r4b.renderers.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r4b.conformance.ProfileUtilities.ElementDefinitionResolution;
|
||||
import org.hl7.fhir.r4b.elementmodel.Element;
|
||||
import org.hl7.fhir.r4b.elementmodel.XmlParser;
|
||||
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r4b.model.Base;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition;
|
||||
import org.hl7.fhir.r4b.model.Narrative.NarrativeStatus;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.renderers.ResourceRenderer;
|
||||
import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.BaseWrapper;
|
||||
import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.PropertyWrapper;
|
||||
import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.RendererWrapperImpl;
|
||||
import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.ResourceWrapper;
|
||||
import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.WrapperBaseImpl;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
||||
public class ElementWrappers {
|
||||
|
||||
public static class BaseWrapperMetaElement extends WrapperBaseImpl implements BaseWrapper {
|
||||
private Element element;
|
||||
private String type;
|
||||
private StructureDefinition structure;
|
||||
private ElementDefinition definition;
|
||||
private List<ElementDefinition> children;
|
||||
private List<PropertyWrapper> list;
|
||||
|
||||
public BaseWrapperMetaElement(RenderingContext context, Element element, String type, StructureDefinition structure, ElementDefinition definition) {
|
||||
super(context);
|
||||
this.element = element;
|
||||
this.type = type;
|
||||
this.structure = structure;
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException {
|
||||
if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element"))
|
||||
return null;
|
||||
|
||||
if (element.hasElementProperty()) {
|
||||
return element;
|
||||
}
|
||||
ByteArrayOutputStream xml = new ByteArrayOutputStream();
|
||||
try {
|
||||
new XmlParser(context.getWorker()).compose(element, xml, OutputStyle.PRETTY, null);
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException(e.getMessage(), e);
|
||||
}
|
||||
if (context.getParser() == null) {
|
||||
System.out.println("No version specific parser provided");
|
||||
}
|
||||
if (context.getParser() == null) {
|
||||
throw new Error("No type parser provided to renderer context");
|
||||
} else {
|
||||
return context.getParser().parseType(xml.toString(), type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PropertyWrapper> children() {
|
||||
if (list == null) {
|
||||
children = context.getProfileUtilities().getChildList(structure, definition);
|
||||
if (children.isEmpty() && !Utilities.noString(type)) {
|
||||
StructureDefinition sd = context.getWorker().fetchTypeDefinition(type);
|
||||
children = context.getProfileUtilities().getChildList(sd, sd.getSnapshot().getElementFirstRep());
|
||||
}
|
||||
list = new ArrayList<PropertyWrapper>();
|
||||
for (ElementDefinition child : children) {
|
||||
List<Element> elements = new ArrayList<Element>();
|
||||
String name = tail(child.getPath());
|
||||
if (name.endsWith("[x]"))
|
||||
element.getNamedChildrenWithWildcard(name, elements);
|
||||
else
|
||||
element.getNamedChildren(name, elements);
|
||||
// if (child.hasContentReference()) {
|
||||
// ElementDefinitionResolution nchild = context.getProfileUtilities().resolveContentRef(structure, child);
|
||||
// if (nchild == null) {
|
||||
// throw new DefinitionException("Unable to resolve content reference "+child.getContentReference());
|
||||
// }
|
||||
// list.add(new PropertyWrapperMetaElement(context, nchild.getSource(), nchild.getElement(), elements));
|
||||
// } else {
|
||||
list.add(new PropertyWrapperMetaElement(context, structure, child, elements));
|
||||
// }
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyWrapper getChildByName(String name) {
|
||||
for (PropertyWrapper p : children())
|
||||
if (p.getName().equals(name))
|
||||
return p;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return element.fhirType();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ResourceWrapperMetaElement extends WrapperBaseImpl implements ResourceWrapper {
|
||||
private Element wrapped;
|
||||
private List<ResourceWrapper> list;
|
||||
private List<PropertyWrapper> list2;
|
||||
private StructureDefinition definition;
|
||||
public ResourceWrapperMetaElement(RenderingContext context, Element wrapped) {
|
||||
super(context);
|
||||
this.wrapped = wrapped;
|
||||
this.definition = wrapped.getProperty().getStructure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceWrapper> getContained() {
|
||||
if (list == null) {
|
||||
List<Element> children = wrapped.getChildrenByName("contained");
|
||||
list = new ArrayList<ResourceWrapper>();
|
||||
for (Element e : children) {
|
||||
list.add(new ResourceWrapperMetaElement(context, e));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return wrapped.getNamedChildValue("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException {
|
||||
Element txt = wrapped.getNamedChild("text");
|
||||
if (txt == null)
|
||||
return null;
|
||||
Element div = txt.getNamedChild("div");
|
||||
if (div == null)
|
||||
return null;
|
||||
else
|
||||
return div.getXhtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return wrapped.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PropertyWrapper> children() {
|
||||
if (list2 == null) {
|
||||
List<ElementDefinition> children = context.getProfileUtilities().getChildList(definition, definition.getSnapshot().getElement().get(0));
|
||||
list2 = new ArrayList<PropertyWrapper>();
|
||||
for (ElementDefinition child : children) {
|
||||
List<Element> elements = new ArrayList<Element>();
|
||||
if (child.getPath().endsWith("[x]"))
|
||||
wrapped.getNamedChildrenWithWildcard(tail(child.getPath()), elements);
|
||||
else
|
||||
wrapped.getNamedChildren(tail(child.getPath()), elements);
|
||||
list2.add(new PropertyWrapperMetaElement(context, definition, child, elements));
|
||||
}
|
||||
}
|
||||
return list2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describe(XhtmlNode x) {
|
||||
if (wrapped.hasChild("title") && wrapped.getChildValue("title") != null) {
|
||||
x.tx(wrapped.getChildValue("title"));
|
||||
} else if (wrapped.hasChild("name") && wrapped.getChildValue("name") != null) {
|
||||
x.tx(wrapped.getChildValue("name"));
|
||||
} else {
|
||||
x.tx("?ngen-1?");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException {
|
||||
if (!x.hasAttribute("xmlns"))
|
||||
x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
||||
String l = wrapped.getChildValue("language");
|
||||
if (!Utilities.noString(l)) {
|
||||
// use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
|
||||
x.setAttribute("lang", l);
|
||||
x.setAttribute("xml:lang", l);
|
||||
}
|
||||
org.hl7.fhir.r4b.elementmodel.Element txt = wrapped.getNamedChild("text");
|
||||
if (txt == null) {
|
||||
txt = new org.hl7.fhir.r4b.elementmodel.Element("text", wrapped.getProperty().getChild(null, "text"));
|
||||
int i = 0;
|
||||
while (i < wrapped.getChildren().size() && (wrapped.getChildren().get(i).getName().equals("id") || wrapped.getChildren().get(i).getName().equals("meta") || wrapped.getChildren().get(i).getName().equals("implicitRules") || wrapped.getChildren().get(i).getName().equals("language")))
|
||||
i++;
|
||||
if (i >= wrapped.getChildren().size())
|
||||
wrapped.getChildren().add(txt);
|
||||
else
|
||||
wrapped.getChildren().add(i, txt);
|
||||
}
|
||||
org.hl7.fhir.r4b.elementmodel.Element st = txt.getNamedChild("status");
|
||||
if (st == null) {
|
||||
st = new org.hl7.fhir.r4b.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
|
||||
txt.getChildren().add(0, st);
|
||||
}
|
||||
st.setValue(status.toCode());
|
||||
org.hl7.fhir.r4b.elementmodel.Element div = txt.getNamedChild("div");
|
||||
if (div == null) {
|
||||
div = new org.hl7.fhir.r4b.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
|
||||
txt.getChildren().add(div);
|
||||
div.setValue(new XhtmlComposer(XhtmlComposer.XML, context.isPretty()).compose(x));
|
||||
}
|
||||
div.setValue(x.toString());
|
||||
div.setXhtml(x);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseWrapper root() {
|
||||
return new BaseWrapperMetaElement(context, wrapped, getName(), definition, definition.getSnapshot().getElementFirstRep());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition getDefinition() {
|
||||
return definition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base getBase() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNarrative() {
|
||||
StructureDefinition sd = definition;
|
||||
while (sd != null) {
|
||||
if ("DomainResource".equals(sd.getType())) {
|
||||
return true;
|
||||
}
|
||||
sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return wrapped.fhirType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyWrapper getChildByName(String name) {
|
||||
for (PropertyWrapper p : children())
|
||||
if (p.getName().equals(name))
|
||||
return p;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {
|
||||
|
||||
private StructureDefinition structure;
|
||||
private ElementDefinition definition;
|
||||
private List<Element> values;
|
||||
private List<BaseWrapper> list;
|
||||
|
||||
public PropertyWrapperMetaElement(RenderingContext context, StructureDefinition structure, ElementDefinition definition, List<Element> values) {
|
||||
super(context);
|
||||
this.structure = structure;
|
||||
this.definition = definition;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return tail(definition.getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValues() {
|
||||
return values.size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BaseWrapper> getValues() {
|
||||
if (list == null) {
|
||||
list = new ArrayList<BaseWrapper>();
|
||||
for (Element e : values) {
|
||||
list.add(new BaseWrapperMetaElement(context, e, e.fhirType(), structure, definition));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeCode() {
|
||||
return definition.typeSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefinition() {
|
||||
return definition.getDefinition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinCardinality() {
|
||||
return definition.getMin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxCardinality() {
|
||||
return "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.valueOf(definition.getMax());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition getStructure() {
|
||||
return structure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseWrapper value() {
|
||||
if (getValues().size() != 1)
|
||||
throw new Error("Access single value, but value count is "+getValues().size());
|
||||
return getValues().get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceWrapper getAsResource() {
|
||||
return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return getTypeCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementDefinition getElementDefinition() {
|
||||
return definition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,621 +0,0 @@
|
|||
package org.hl7.fhir.r4b.terminologies;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.NoTerminologyServiceException;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext.ValidationResult;
|
||||
import org.hl7.fhir.r4b.model.CanonicalType;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem.CodeSystemContentMode;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionDesignationComponent;
|
||||
import org.hl7.fhir.r4b.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4b.model.Coding;
|
||||
import org.hl7.fhir.r4b.model.Enumerations.PublicationStatus;
|
||||
import org.hl7.fhir.r4b.model.UriType;
|
||||
import org.hl7.fhir.r4b.model.ValueSet;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptReferenceComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptReferenceDesignationComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetFilterComponent;
|
||||
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
|
||||
|
||||
public class ValueSetCheckerSimple implements ValueSetChecker {
|
||||
|
||||
private ValueSet valueset;
|
||||
private IWorkerContext context;
|
||||
private Map<String, ValueSetCheckerSimple> inner = new HashMap<>();
|
||||
private ValidationOptions options;
|
||||
|
||||
public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context) {
|
||||
this.valueset = source;
|
||||
this.context = context;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public ValidationResult validateCode(CodeableConcept code) throws FHIRException {
|
||||
// first, we validate the codings themselves
|
||||
List<String> errors = new ArrayList<String>();
|
||||
List<String> warnings = new ArrayList<String>();
|
||||
if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) {
|
||||
for (Coding c : code.getCoding()) {
|
||||
if (!c.hasSystem()) {
|
||||
warnings.add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE));
|
||||
}
|
||||
CodeSystem cs = context.fetchCodeSystem(c.getSystem());
|
||||
ValidationResult res = null;
|
||||
if (cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) {
|
||||
res = context.validateCode(options.noClient(), c, null);
|
||||
} else {
|
||||
res = validateCode(c, cs);
|
||||
}
|
||||
if (!res.isOk()) {
|
||||
errors.add(res.getMessage());
|
||||
} else if (res.getMessage() != null) {
|
||||
warnings.add(res.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) {
|
||||
Boolean result = false;
|
||||
for (Coding c : code.getCoding()) {
|
||||
Boolean ok = codeInValueSet(c.getSystem(), c.getCode());
|
||||
if (ok == null && result == false) {
|
||||
result = null;
|
||||
} else if (ok) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
warnings.add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl()));
|
||||
} else if (!result) {
|
||||
errors.add(0, context.formatMessage(I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl()));
|
||||
}
|
||||
}
|
||||
if (errors.size() > 0) {
|
||||
return new ValidationResult(IssueSeverity.ERROR, errors.toString());
|
||||
} else if (warnings.size() > 0) {
|
||||
return new ValidationResult(IssueSeverity.WARNING, warnings.toString());
|
||||
} else {
|
||||
return new ValidationResult(IssueSeverity.INFORMATION, null);
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationResult validateCode(Coding code) throws FHIRException {
|
||||
String warningMessage = null;
|
||||
// first, we validate the concept itself
|
||||
|
||||
ValidationResult res = null;
|
||||
boolean inExpansion = false;
|
||||
String system = code.hasSystem() ? code.getSystem() : getValueSetSystem();
|
||||
if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) {
|
||||
if (system == null && !code.hasDisplay()) { // dealing with just a plain code (enum)
|
||||
system = systemForCodeInValueSet(code.getCode());
|
||||
}
|
||||
if (!code.hasSystem()) {
|
||||
if (options.isGuessSystem() && system == null && Utilities.isAbsoluteUrl(code.getCode())) {
|
||||
system = "urn:ietf:rfc:3986"; // this arises when using URIs bound to value sets
|
||||
}
|
||||
code.setSystem(system);
|
||||
}
|
||||
inExpansion = checkExpansion(code);
|
||||
CodeSystem cs = context.fetchCodeSystem(system);
|
||||
if (cs == null) {
|
||||
cs = findSpecialCodeSystem(system);
|
||||
}
|
||||
if (cs == null) {
|
||||
warningMessage = "Unable to resolve system "+system;
|
||||
if (!inExpansion) {
|
||||
throw new FHIRException(warningMessage);
|
||||
}
|
||||
}
|
||||
if (cs != null && cs.hasSupplements()) {
|
||||
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl()));
|
||||
}
|
||||
if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) {
|
||||
warningMessage = "Resolved system "+system+", but the definition is not complete";
|
||||
if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment
|
||||
throw new FHIRException(warningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (cs != null /*&& (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)*/) {
|
||||
if (!(cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
|
||||
// we can't validate that here.
|
||||
return new ValidationResult(IssueSeverity.ERROR, "Unable to evaluate based on empty code system");
|
||||
}
|
||||
res = validateCode(code, cs);
|
||||
} else if (cs == null && valueset.hasExpansion() && inExpansion) {
|
||||
// we just take the value set as face value then
|
||||
res = new ValidationResult(IssueSeverity.INFORMATION, null);
|
||||
} else {
|
||||
// well, we didn't find a code system - try the expansion?
|
||||
// disabled waiting for discussion
|
||||
throw new FHIRException("No try the server");
|
||||
}
|
||||
} else {
|
||||
// disabled waiting for discussion
|
||||
throw new FHIRException("No try the server");
|
||||
// inExpansion = checkExpansion(code);
|
||||
}
|
||||
|
||||
// then, if we have a value set, we check it's in the value set
|
||||
if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) {
|
||||
if ((res==null || res.isOk()) && !codeInValueSet(system, code.getCode())) {
|
||||
if (!inExpansion) {
|
||||
res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR);
|
||||
} else if (warningMessage!=null) {
|
||||
res = new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage));
|
||||
} else {
|
||||
res.setMessage("Code found in expansion, however: " + res.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private CodeSystem findSpecialCodeSystem(String system) {
|
||||
if ("urn:ietf:rfc:3986".equals(system)) {
|
||||
CodeSystem cs = new CodeSystem();
|
||||
cs.setUrl(system);
|
||||
cs.setUserData("tx.cs.special", new URICodeSystem());
|
||||
cs.setContent(CodeSystemContentMode.COMPLETE);
|
||||
return cs;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationResult findCodeInExpansion(Coding code) {
|
||||
if (valueset==null || !valueset.hasExpansion())
|
||||
return null;
|
||||
return findCodeInExpansion(code, valueset.getExpansion().getContains());
|
||||
}
|
||||
|
||||
private ValidationResult findCodeInExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) {
|
||||
for (ValueSetExpansionContainsComponent containsComponent: contains) {
|
||||
if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
|
||||
ConceptDefinitionComponent ccd = new ConceptDefinitionComponent();
|
||||
ccd.setCode(containsComponent.getCode());
|
||||
ccd.setDisplay(containsComponent.getDisplay());
|
||||
ValidationResult res = new ValidationResult(ccd);
|
||||
return res;
|
||||
}
|
||||
if (containsComponent.hasContains()) {
|
||||
ValidationResult res = findCodeInExpansion(code, containsComponent.getContains());
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkExpansion(Coding code) {
|
||||
if (valueset==null || !valueset.hasExpansion()) {
|
||||
return false;
|
||||
}
|
||||
return checkExpansion(code, valueset.getExpansion().getContains());
|
||||
}
|
||||
|
||||
private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) {
|
||||
for (ValueSetExpansionContainsComponent containsComponent: contains) {
|
||||
if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
|
||||
return true;
|
||||
}
|
||||
if (containsComponent.hasContains() && checkExpansion(code, containsComponent.getContains())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ValidationResult validateCode(Coding code, CodeSystem cs) {
|
||||
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode());
|
||||
if (cc == null) {
|
||||
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
|
||||
return new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_FRAGMENT, gen(code), cs.getUrl()));
|
||||
} else {
|
||||
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_, gen(code), cs.getUrl()));
|
||||
}
|
||||
}
|
||||
if (code.getDisplay() == null) {
|
||||
return new ValidationResult(cc);
|
||||
}
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
if (cc.hasDisplay()) {
|
||||
b.append(cc.getDisplay());
|
||||
if (code.getDisplay().equalsIgnoreCase(cc.getDisplay())) {
|
||||
return new ValidationResult(cc);
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) {
|
||||
b.append(ds.getValue());
|
||||
if (code.getDisplay().equalsIgnoreCase(ds.getValue())) {
|
||||
return new ValidationResult(cc);
|
||||
}
|
||||
}
|
||||
// also check to see if the value set has another display
|
||||
ConceptReferenceComponent vs = findValueSetRef(code.getSystem(), code.getCode());
|
||||
if (vs != null && (vs.hasDisplay() ||vs.hasDesignation())) {
|
||||
if (vs.hasDisplay()) {
|
||||
b.append(vs.getDisplay());
|
||||
if (code.getDisplay().equalsIgnoreCase(vs.getDisplay())) {
|
||||
return new ValidationResult(cc);
|
||||
}
|
||||
}
|
||||
for (ConceptReferenceDesignationComponent ds : vs.getDesignation()) {
|
||||
b.append(ds.getValue());
|
||||
if (code.getDisplay().equalsIgnoreCase(ds.getValue())) {
|
||||
return new ValidationResult(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF_, code.getSystem(), code.getCode(), b.toString(), code.getDisplay()), cc);
|
||||
}
|
||||
|
||||
private ConceptReferenceComponent findValueSetRef(String system, String code) {
|
||||
if (valueset == null)
|
||||
return null;
|
||||
// if it has an expansion
|
||||
for (ValueSetExpansionContainsComponent exp : valueset.getExpansion().getContains()) {
|
||||
if (system.equals(exp.getSystem()) && code.equals(exp.getCode())) {
|
||||
ConceptReferenceComponent cc = new ConceptReferenceComponent();
|
||||
cc.setDisplay(exp.getDisplay());
|
||||
cc.setDesignation(exp.getDesignation());
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent inc : valueset.getCompose().getInclude()) {
|
||||
if (system.equals(inc.getSystem())) {
|
||||
for (ConceptReferenceComponent cc : inc.getConcept()) {
|
||||
if (cc.getCode().equals(code)) {
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (CanonicalType url : inc.getValueSet()) {
|
||||
ConceptReferenceComponent cc = getVs(url.asStringValue()).findValueSetRef(system, code);
|
||||
if (cc != null) {
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String gen(Coding code) {
|
||||
if (code.hasSystem()) {
|
||||
return code.getSystem()+"#"+code.getCode();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getValueSetSystem() throws FHIRException {
|
||||
if (valueset == null) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__NO_VALUE_SET));
|
||||
}
|
||||
if (valueset.getCompose().getInclude().size() == 0) {
|
||||
if (!valueset.hasExpansion() || valueset.getExpansion().getContains().size() == 0) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_NO_INCLUDES_OR_EXPANSION));
|
||||
} else {
|
||||
String cs = valueset.getExpansion().getContains().get(0).getSystem();
|
||||
if (cs != null && checkSystem(valueset.getExpansion().getContains(), cs)) {
|
||||
return cs;
|
||||
} else {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_EXPANSION_HAS_MULTIPLE_SYSTEMS));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent inc : valueset.getCompose().getInclude()) {
|
||||
if (inc.hasValueSet()) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_IMPORTS));
|
||||
}
|
||||
if (!inc.hasSystem()) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM));
|
||||
}
|
||||
}
|
||||
if (valueset.getCompose().getInclude().size() == 1) {
|
||||
return valueset.getCompose().getInclude().get(0).getSystem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that all system values within an expansion correspond to the specified system value
|
||||
*/
|
||||
private boolean checkSystem(List<ValueSetExpansionContainsComponent> containsList, String system) {
|
||||
for (ValueSetExpansionContainsComponent contains : containsList) {
|
||||
if (!contains.getSystem().equals(system) || (contains.hasContains() && !checkSystem(contains.getContains(), system))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code) {
|
||||
if (code.equals(concept.getCode())) {
|
||||
return concept;
|
||||
}
|
||||
ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code);
|
||||
if (cc != null) {
|
||||
return cc;
|
||||
}
|
||||
if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
|
||||
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
|
||||
for (ConceptDefinitionComponent c : children) {
|
||||
cc = findCodeInConcept(c, code);
|
||||
if (cc != null) {
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code) {
|
||||
for (ConceptDefinitionComponent cc : concept) {
|
||||
if (code.equals(cc.getCode())) {
|
||||
return cc;
|
||||
}
|
||||
ConceptDefinitionComponent c = findCodeInConcept(cc, code);
|
||||
if (c != null) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private String systemForCodeInValueSet(String code) {
|
||||
String sys = null;
|
||||
if (valueset.hasCompose()) {
|
||||
if (valueset.getCompose().hasExclude()) {
|
||||
return null;
|
||||
}
|
||||
for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) {
|
||||
if (vsi.hasValueSet()) {
|
||||
return null;
|
||||
}
|
||||
if (!vsi.hasSystem()) {
|
||||
return null;
|
||||
}
|
||||
if (vsi.hasFilter()) {
|
||||
return null;
|
||||
}
|
||||
CodeSystem cs = context.fetchCodeSystem(vsi.getSystem());
|
||||
if (cs == null) {
|
||||
return null;
|
||||
}
|
||||
if (vsi.hasConcept()) {
|
||||
for (ConceptReferenceComponent cc : vsi.getConcept()) {
|
||||
boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code);
|
||||
if (match) {
|
||||
if (sys == null) {
|
||||
sys = vsi.getSystem();
|
||||
} else if (!sys.equals(vsi.getSystem())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code);
|
||||
if (cc != null) {
|
||||
if (sys == null) {
|
||||
sys = vsi.getSystem();
|
||||
} else if (!sys.equals(vsi.getSystem())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean codeInValueSet(String system, String code) throws FHIRException {
|
||||
Boolean result = false;
|
||||
if (valueset.hasExpansion()) {
|
||||
return checkExpansion(new Coding(system, code, null));
|
||||
} else if (valueset.hasCompose()) {
|
||||
for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) {
|
||||
Boolean ok = inComponent(vsi, system, code, valueset.getCompose().getInclude().size() == 1);
|
||||
if (ok == null && result == false) {
|
||||
result = null;
|
||||
} else if (ok) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) {
|
||||
Boolean nok = inComponent(vsi, system, code, valueset.getCompose().getInclude().size() == 1);
|
||||
if (nok == null && result == false) {
|
||||
result = null;
|
||||
} else if (nok) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Boolean inComponent(ConceptSetComponent vsi, String system, String code, boolean only) throws FHIRException {
|
||||
for (UriType uri : vsi.getValueSet()) {
|
||||
if (inImport(uri.getValue(), system, code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vsi.hasSystem()) {
|
||||
return false;
|
||||
}
|
||||
if (only && system == null) {
|
||||
// whether we know the system or not, we'll accept the stated codes at face value
|
||||
for (ConceptReferenceComponent cc : vsi.getConcept()) {
|
||||
if (cc.getCode().equals(code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!system.equals(vsi.getSystem()))
|
||||
return false;
|
||||
// ok, we need the code system
|
||||
CodeSystem cs = context.fetchCodeSystem(system);
|
||||
if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) {
|
||||
// make up a transient value set with
|
||||
ValueSet vs = new ValueSet();
|
||||
vs.setStatus(PublicationStatus.ACTIVE);
|
||||
vs.setUrl(Utilities.makeUuidUrn());
|
||||
vs.getCompose().addInclude(vsi);
|
||||
ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs);
|
||||
if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) {
|
||||
return null;
|
||||
}
|
||||
if (res.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) {
|
||||
throw new NoTerminologyServiceException();
|
||||
}
|
||||
return res.isOk();
|
||||
} else {
|
||||
if (vsi.hasFilter()) {
|
||||
boolean ok = true;
|
||||
for (ConceptSetFilterComponent f : vsi.getFilter()) {
|
||||
if (!codeInFilter(cs, system, f, code)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
List<ConceptDefinitionComponent> list = cs.getConcept();
|
||||
boolean ok = validateCodeInConceptList(code, cs, list);
|
||||
if (ok && vsi.hasConcept()) {
|
||||
for (ConceptReferenceComponent cc : vsi.getConcept()) {
|
||||
if (cc.getCode().equals(code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean codeInFilter(CodeSystem cs, String system, ConceptSetFilterComponent f, String code) throws FHIRException {
|
||||
if ("concept".equals(f.getProperty()))
|
||||
return codeInConceptFilter(cs, f, code);
|
||||
else {
|
||||
System.out.println("todo: handle filters with property = "+f.getProperty());
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__FILTER_WITH_PROPERTY__, cs.getUrl(), f.getProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean codeInConceptFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) throws FHIRException {
|
||||
switch (f.getOp()) {
|
||||
case ISA: return codeInConceptIsAFilter(cs, f, code);
|
||||
case ISNOTA: return !codeInConceptIsAFilter(cs, f, code);
|
||||
default:
|
||||
System.out.println("todo: handle concept filters with op = "+f.getOp());
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__, cs.getUrl(), f.getOp()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) {
|
||||
if (code.equals(f.getProperty())) {
|
||||
return true;
|
||||
}
|
||||
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue());
|
||||
if (cc == null) {
|
||||
return false;
|
||||
}
|
||||
cc = findCodeInConcept(cc, code);
|
||||
return cc != null;
|
||||
}
|
||||
|
||||
public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list) {
|
||||
if (def.getCaseSensitive()) {
|
||||
for (ConceptDefinitionComponent cc : list) {
|
||||
if (cc.getCode().equals(code)) {
|
||||
return true;
|
||||
}
|
||||
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (ConceptDefinitionComponent cc : list) {
|
||||
if (cc.getCode().equalsIgnoreCase(code)) {
|
||||
return true;
|
||||
}
|
||||
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ValueSetCheckerSimple getVs(String url) {
|
||||
if (inner.containsKey(url)) {
|
||||
return inner.get(url);
|
||||
}
|
||||
ValueSet vs = context.fetchResource(ValueSet.class, url);
|
||||
ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, context);
|
||||
inner.put(url, vsc);
|
||||
return vsc;
|
||||
}
|
||||
|
||||
private boolean inImport(String uri, String system, String code) throws FHIRException {
|
||||
return getVs(uri).codeInValueSet(system, code);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,8 +14,11 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.fhir.ucum.Decimal;
|
||||
import org.fhir.ucum.Pair;
|
||||
import org.fhir.ucum.UcumException;
|
||||
|
@ -235,6 +238,18 @@ public class FHIRPathEngine {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
public boolean hasType(String tn) {
|
||||
if (type != null) {
|
||||
return tn.equals(type);
|
||||
} else {
|
||||
for (TypeRefComponent t : element.getType()) {
|
||||
if (tn.equals(t.getCode())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private IWorkerContext worker;
|
||||
private IEvaluationContext hostServices;
|
||||
|
@ -1000,7 +1015,9 @@ public class FHIRPathEngine {
|
|||
wrapper.setProximal(proximal);
|
||||
}
|
||||
|
||||
if (lexer.isConstant()) {
|
||||
if (lexer.getCurrent() == null) {
|
||||
throw lexer.error("Expression terminated unexpectedly");
|
||||
} else if (lexer.isConstant()) {
|
||||
boolean isString = lexer.isStringConstant();
|
||||
if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) {
|
||||
// the grammar says that this is a unary operation; it affects the correct processing order of the inner operations
|
||||
|
@ -1308,6 +1325,7 @@ public class FHIRPathEngine {
|
|||
case StartsWith: return checkParamCount(lexer, location, exp, 1);
|
||||
case EndsWith: return checkParamCount(lexer, location, exp, 1);
|
||||
case Matches: return checkParamCount(lexer, location, exp, 1);
|
||||
case MatchesFull: return checkParamCount(lexer, location, exp, 1);
|
||||
case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
|
||||
case Contains: return checkParamCount(lexer, location, exp, 1);
|
||||
case Replace: return checkParamCount(lexer, location, exp, 2);
|
||||
|
@ -1363,6 +1381,10 @@ public class FHIRPathEngine {
|
|||
case Log: return checkParamCount(lexer, location, exp, 1);
|
||||
case Power: return checkParamCount(lexer, location, exp, 1);
|
||||
case Truncate: return checkParamCount(lexer, location, exp, 0);
|
||||
case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1);
|
||||
case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1);
|
||||
case Precision: return checkParamCount(lexer, location, exp, 0);
|
||||
|
||||
case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
|
||||
}
|
||||
return false;
|
||||
|
@ -2365,7 +2387,7 @@ public class FHIRPathEngine {
|
|||
if (vs != null) {
|
||||
for (Base l : left) {
|
||||
if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
|
||||
if (worker.validateCode(terminologyServiceOptions , TypeConvertor.castToCoding(l), vs).isOk()) {
|
||||
if (worker.validateCode(terminologyServiceOptions.guessSystem() , TypeConvertor.castToCoding(l), vs).isOk()) {
|
||||
ans = true;
|
||||
}
|
||||
} else if (l.fhirType().equals("Coding")) {
|
||||
|
@ -2928,7 +2950,7 @@ public class FHIRPathEngine {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
|
||||
if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
|
||||
StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType());
|
||||
if (sd == null) {
|
||||
// logical model
|
||||
|
@ -2988,12 +3010,14 @@ public class FHIRPathEngine {
|
|||
if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) {
|
||||
paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
|
||||
} else {
|
||||
int i = 0;
|
||||
for (ExpressionNode expr : exp.getParameters()) {
|
||||
if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) {
|
||||
if (isExpressionParameter(exp, i)) {
|
||||
paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
|
||||
} else {
|
||||
paramTypes.add(executeType(context, focus, expr, true));
|
||||
paramTypes.add(executeType(context, context.thisItem, expr, true));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
switch (exp.getFunction()) {
|
||||
|
@ -3142,6 +3166,11 @@ public class FHIRPathEngine {
|
|||
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||
}
|
||||
case MatchesFull : {
|
||||
checkContextString(focus, "matches", exp);
|
||||
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||
}
|
||||
case ReplaceMatches : {
|
||||
checkContextString(focus, "replaceMatches", exp);
|
||||
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
|
||||
|
@ -3312,6 +3341,25 @@ public class FHIRPathEngine {
|
|||
return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());
|
||||
}
|
||||
|
||||
case LowBoundary:
|
||||
case HighBoundary: {
|
||||
checkContextContinuous(focus, exp.getFunction().toCode(), exp);
|
||||
if (paramTypes.size() > 0) {
|
||||
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
|
||||
}
|
||||
if (focus.hasType("decimal") && (focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);
|
||||
} else if (focus.hasType("decimal")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
|
||||
} else {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
|
||||
}
|
||||
}
|
||||
case Precision: {
|
||||
checkContextContinuous(focus, exp.getFunction().toCode(), exp);
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
|
||||
}
|
||||
|
||||
case Custom : {
|
||||
return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
|
||||
}
|
||||
|
@ -3321,6 +3369,17 @@ public class FHIRPathEngine {
|
|||
throw new Error("not Implemented yet");
|
||||
}
|
||||
|
||||
private boolean isExpressionParameter(ExpressionNode exp, int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate;
|
||||
case 1:
|
||||
return exp.getFunction() == Function.Trace;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkParamTypes(ExpressionNode expr, String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
|
||||
int i = 0;
|
||||
|
@ -3387,6 +3446,12 @@ public class FHIRPathEngine {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
|
||||
if (!focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe());
|
||||
}
|
||||
}
|
||||
|
||||
private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
|
||||
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
|
||||
for (String f : focus.getTypes()) {
|
||||
|
@ -3442,6 +3507,7 @@ public class FHIRPathEngine {
|
|||
case StartsWith : return funcStartsWith(context, focus, exp);
|
||||
case EndsWith : return funcEndsWith(context, focus, exp);
|
||||
case Matches : return funcMatches(context, focus, exp);
|
||||
case MatchesFull : return funcMatchesFull(context, focus, exp);
|
||||
case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
|
||||
case Contains : return funcContains(context, focus, exp);
|
||||
case Replace : return funcReplace(context, focus, exp);
|
||||
|
@ -3497,6 +3563,10 @@ public class FHIRPathEngine {
|
|||
case Log : return funcLog(context, focus, exp);
|
||||
case Power : return funcPower(context, focus, exp);
|
||||
case Truncate : return funcTruncate(context, focus, exp);
|
||||
case LowBoundary : return funcLowBoundary(context, focus, exp);
|
||||
case HighBoundary : return funcHighBoundary(context, focus, exp);
|
||||
case Precision : return funcPrecision(context, focus, exp);
|
||||
|
||||
|
||||
case Custom: {
|
||||
List<List<Base>> params = new ArrayList<List<Base>>();
|
||||
|
@ -3702,6 +3772,84 @@ public class FHIRPathEngine {
|
|||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "lowBoundary", focus.size());
|
||||
}
|
||||
int precision = 0;
|
||||
if (expr.getParameters().size() > 0) {
|
||||
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
|
||||
if (n1.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
|
||||
}
|
||||
precision = Integer.parseInt(n1.get(0).primitiveValue());
|
||||
}
|
||||
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision)));
|
||||
} else if (base.hasType("date")) {
|
||||
result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision)));
|
||||
} else if (base.hasType("dateTime")) {
|
||||
result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "highBoundary", focus.size());
|
||||
}
|
||||
int precision = 0;
|
||||
if (expr.getParameters().size() > 0) {
|
||||
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
|
||||
if (n1.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
|
||||
}
|
||||
precision = Integer.parseInt(n1.get(0).primitiveValue());
|
||||
}
|
||||
|
||||
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision)));
|
||||
} else if (base.hasType("date")) {
|
||||
result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision)));
|
||||
} else if (base.hasType("dateTime")) {
|
||||
result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "highBoundary", focus.size());
|
||||
}
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue())));
|
||||
} else if (base.hasType("date") || base.hasType("dateTime")) {
|
||||
result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue())));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue())));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "round", focus.size());
|
||||
|
@ -4229,7 +4377,7 @@ public class FHIRPathEngine {
|
|||
result.add(item);
|
||||
}
|
||||
}
|
||||
for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
|
||||
for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) {
|
||||
if (!doContains(result, item)) {
|
||||
result.add(item);
|
||||
}
|
||||
|
@ -4378,10 +4526,21 @@ public class FHIRPathEngine {
|
|||
pc.add(item);
|
||||
added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
|
||||
}
|
||||
more = !added.isEmpty();
|
||||
result.addAll(added);
|
||||
more = false;
|
||||
current.clear();
|
||||
current.addAll(added);
|
||||
for (Base b : added) {
|
||||
boolean isnew = true;
|
||||
for (Base t : result) {
|
||||
if (b.equalsDeep(t)) {
|
||||
isnew = false;
|
||||
}
|
||||
}
|
||||
if (isnew) {
|
||||
result.add(b);
|
||||
current.add(b);
|
||||
more = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -4763,7 +4922,29 @@ public class FHIRPathEngine {
|
|||
if (Utilities.noString(st)) {
|
||||
result.add(new BooleanType(false).noExtensions());
|
||||
} else {
|
||||
boolean ok = st.matches("(?s)" + sw);
|
||||
Pattern p = Pattern.compile("(?s)" + sw);
|
||||
Matcher m = p.matcher(st);
|
||||
boolean ok = m.find();
|
||||
result.add(new BooleanType(ok).noExtensions());
|
||||
}
|
||||
} else {
|
||||
result.add(new BooleanType(false).noExtensions());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
|
||||
|
||||
if (focus.size() == 1 && !Utilities.noString(sw)) {
|
||||
String st = convertToString(focus.get(0));
|
||||
if (Utilities.noString(st)) {
|
||||
result.add(new BooleanType(false).noExtensions());
|
||||
} else {
|
||||
Pattern p = Pattern.compile("(?s)" + sw);
|
||||
Matcher m = p.matcher(st);
|
||||
boolean ok = m.matches();
|
||||
result.add(new BooleanType(ok).noExtensions());
|
||||
}
|
||||
} else {
|
||||
|
@ -4774,7 +4955,7 @@ public class FHIRPathEngine {
|
|||
|
||||
private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
|
||||
String sw = convertToString(execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true));
|
||||
|
||||
if (focus.size() != 1) {
|
||||
result.add(new BooleanType(false).noExtensions());
|
||||
|
@ -4791,6 +4972,12 @@ public class FHIRPathEngine {
|
|||
return result;
|
||||
}
|
||||
|
||||
private List<Base> baseToList(Base b) {
|
||||
List<Base> res = new ArrayList<>();
|
||||
res.add(b);
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (focus.size() == 1) {
|
||||
|
@ -5470,7 +5657,7 @@ public class FHIRPathEngine {
|
|||
* @throws PathEngineException
|
||||
* @throws DefinitionException
|
||||
*/
|
||||
public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source) throws DefinitionException {
|
||||
public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException {
|
||||
StructureDefinition sd = profile;
|
||||
TypedElementDefinition focus = null;
|
||||
boolean okToNotResolve = false;
|
||||
|
@ -5582,10 +5769,19 @@ public class FHIRPathEngine {
|
|||
} else {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl());
|
||||
}
|
||||
} else if (expr.getInner() == null) {
|
||||
} else {
|
||||
// gdg 26-02-2022. If we're walking towards a resolve() and we're on a reference, and we try to walk into the reference
|
||||
// then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined
|
||||
// on the Reference, not the reference.reference.
|
||||
ExpressionNode next = expr.getInner();
|
||||
if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) {
|
||||
next = next.getInner();
|
||||
}
|
||||
if (next == null) {
|
||||
return focus;
|
||||
} else {
|
||||
return evaluateDefinition(expr.getInner(), sd, focus, profile);
|
||||
return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5732,4 +5928,4 @@ public class FHIRPathEngine {
|
|||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,215 +0,0 @@
|
|||
package org.hl7.fhir.r4b.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4b.context.IWorkerContext;
|
||||
import org.hl7.fhir.r4b.model.Constants;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition;
|
||||
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r4b.model.Enumerations.FHIRVersion;
|
||||
import org.hl7.fhir.r4b.model.Enumerations.PublicationStatus;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.ExtensionContextType;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
|
||||
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
|
||||
import org.hl7.fhir.r4b.model.UriType;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.json.JsonTrackingParser;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class XVerExtensionManager {
|
||||
|
||||
public enum XVerExtensionStatus {
|
||||
BadVersion, Unknown, Invalid, Valid
|
||||
}
|
||||
|
||||
public static final String XVER_EXT_MARKER = "XVER_EXT_MARKER";
|
||||
|
||||
private Map<String, JsonObject> lists = new HashMap<>();
|
||||
private IWorkerContext context;
|
||||
|
||||
public XVerExtensionManager(IWorkerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public XVerExtensionStatus status(String url) throws FHIRException {
|
||||
String v = url.substring(20, 23);
|
||||
if ("5.0".equals(v)) {
|
||||
v = Constants.VERSION_MM;
|
||||
}
|
||||
String e = url.substring(54);
|
||||
if (!lists.containsKey(v)) {
|
||||
if (context.getBinaries().containsKey("xver-paths-"+v+".json")) {
|
||||
try {
|
||||
lists.put(v, JsonTrackingParser.parseJson(context.getBinaries().get("xver-paths-"+v+".json")));
|
||||
} catch (IOException e1) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
} else {
|
||||
return XVerExtensionStatus.BadVersion;
|
||||
}
|
||||
}
|
||||
JsonObject root = lists.get(v);
|
||||
JsonObject path = root.getAsJsonObject(e);
|
||||
if (path == null) {
|
||||
return XVerExtensionStatus.Unknown;
|
||||
}
|
||||
if (path.has("elements") || path.has("types")) {
|
||||
return XVerExtensionStatus.Valid;
|
||||
} else {
|
||||
return XVerExtensionStatus.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
public String getElementId(String url) {
|
||||
return url.substring(54);
|
||||
}
|
||||
|
||||
public StructureDefinition makeDefinition(String url) {
|
||||
String verSource = url.substring(20, 23);
|
||||
if ("5.0".equals(verSource)) {
|
||||
verSource = Constants.VERSION_MM;
|
||||
}
|
||||
String verTarget = VersionUtilities.getMajMin(context.getVersion());
|
||||
String e = url.substring(54);
|
||||
JsonObject root = lists.get(verSource);
|
||||
JsonObject path = root.getAsJsonObject(e);
|
||||
|
||||
StructureDefinition sd = new StructureDefinition();
|
||||
sd.setUserData(XVER_EXT_MARKER, "true");
|
||||
sd.setUserData("path", "https://hl7.org/fhir/versions.html#extensions");
|
||||
sd.setUrl(url);
|
||||
sd.setVersion(context.getVersion());
|
||||
sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion()));
|
||||
sd.setKind(StructureDefinitionKind.COMPLEXTYPE);
|
||||
sd.setType("Extension");
|
||||
sd.setDerivation(TypeDerivationRule.CONSTRAINT);
|
||||
sd.setName("Extension-"+verSource+"-"+e);
|
||||
sd.setTitle("Extension Definition for "+e+" for Version "+verSource);
|
||||
sd.setStatus(PublicationStatus.ACTIVE);
|
||||
sd.setExperimental(false);
|
||||
sd.setDate(new Date());
|
||||
sd.setPublisher("FHIR Project");
|
||||
sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)");
|
||||
sd.setAbstract(false);
|
||||
sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e));
|
||||
sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||
if (path.has("types")) {
|
||||
sd.getDifferential().addElement().setPath("Extension.extension").setMax("0");
|
||||
sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
|
||||
ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1);
|
||||
populateTypes(path, val, verSource, verTarget);
|
||||
} else if (path.has("elements")) {
|
||||
for (JsonElement i : path.getAsJsonArray("elements")) {
|
||||
String s = i.getAsString();
|
||||
sd.getDifferential().addElement().setPath("Extension.extension").setSliceName(s);
|
||||
sd.getDifferential().addElement().setPath("Extension.extension.extension").setMax("0");
|
||||
sd.getDifferential().addElement().setPath("Extension.extension.url").setFixed(new UriType(s));
|
||||
ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.extension.value[x]").setMin(1);
|
||||
JsonObject elt = root.getAsJsonObject(e+"."+s);
|
||||
if (!elt.has("types")) {
|
||||
throw new FHIRException("Internal error - nested elements not supported yet");
|
||||
}
|
||||
populateTypes(elt, val, verSource, verTarget);
|
||||
}
|
||||
sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
|
||||
sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0");
|
||||
} else {
|
||||
throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid");
|
||||
}
|
||||
return sd;
|
||||
}
|
||||
|
||||
public void populateTypes(JsonObject path, ElementDefinition val, String verSource, String verTarget) {
|
||||
for (JsonElement i : path.getAsJsonArray("types")) {
|
||||
String s = i.getAsString();
|
||||
if (s.contains("(")) {
|
||||
String t = s.substring(0, s.indexOf("("));
|
||||
TypeRefComponent tr = val.addType().setCode(translateDataType(verTarget, t));
|
||||
if (hasTargets(tr.getCode()) ) {
|
||||
s = s.substring(t.length()+1);
|
||||
for (String p : s.substring(0, s.length()-1).split("\\|")) {
|
||||
if ("Any".equals(p)) {
|
||||
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource");
|
||||
} else {
|
||||
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val.addType().setCode(translateDataType(verTarget, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTargets(String dt) {
|
||||
return Utilities.existsInList(dt, "canonical", "Reference", "CodeableReference");
|
||||
}
|
||||
|
||||
private String translateDataType(String v, String dt) {
|
||||
if (VersionUtilities.versionsCompatible("1.0", v) || VersionUtilities.versionsCompatible("1.4", v)) {
|
||||
return translateToR2(dt);
|
||||
} else if (VersionUtilities.versionsCompatible("3.0", v)) {
|
||||
return translateToR3(dt);
|
||||
} else {
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
|
||||
private String translateToR3(String dt) {
|
||||
if ("canonical".equals(dt)) {
|
||||
return "uri";
|
||||
} else if ("url".equals(dt)) {
|
||||
return "uri";
|
||||
} else {
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
|
||||
private String translateToR2(String dt) {
|
||||
if ("canonical".equals(dt)) {
|
||||
return "uri";
|
||||
} else if ("url".equals(dt)) {
|
||||
return "uri";
|
||||
} else if ("uuid".equals(dt)) {
|
||||
return "id";
|
||||
} else {
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
|
||||
private String head(String id) {
|
||||
if (id.contains(".")) {
|
||||
return id.substring(0, id.lastIndexOf("."));
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public String getVersion(String url) {
|
||||
return url.substring(20, 23);
|
||||
}
|
||||
|
||||
public boolean matchingUrl(String url) {
|
||||
if (url == null || url.length() < 56) {
|
||||
return false;
|
||||
}
|
||||
String pfx = url.substring(0, 20);
|
||||
String v = url.substring(20, 23);
|
||||
String sfx = url.substring(23, 54);
|
||||
return pfx.equals("http://hl7.org/fhir/") &&
|
||||
isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-");
|
||||
}
|
||||
|
||||
private boolean isVersionPattern(String v) {
|
||||
return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,573 +0,0 @@
|
|||
package org.hl7.fhir.r4b.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of HL7 nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.r4b.model.*;
|
||||
import org.hl7.fhir.r4b.model.Parameters.ParametersParameterComponent;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ByteUtils;
|
||||
import org.hl7.fhir.r4b.utils.client.network.Client;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ResourceRequest;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Very Simple RESTful client. This is purely for use in the standalone
|
||||
* tools jar packages. It doesn't support many features, only what the tools
|
||||
* need.
|
||||
* <p>
|
||||
* To use, initialize class and set base service URI as follows:
|
||||
*
|
||||
* <pre><code>
|
||||
* FHIRSimpleClient fhirClient = new FHIRSimpleClient();
|
||||
* fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json.
|
||||
* <p>
|
||||
* These can be changed by invoking the following setter functions:
|
||||
*
|
||||
* <pre><code>
|
||||
* setPreferredResourceFormat()
|
||||
* setPreferredFeedFormat()
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* TODO Review all sad paths.
|
||||
*
|
||||
* @author Claude Nanjo
|
||||
*/
|
||||
public class FHIRToolingClient {
|
||||
|
||||
public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK";
|
||||
public static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
public static final String hostKey = "http.proxyHost";
|
||||
public static final String portKey = "http.proxyPort";
|
||||
|
||||
private static final int TIMEOUT_NORMAL = 1500;
|
||||
private static final int TIMEOUT_OPERATION = 30000;
|
||||
private static final int TIMEOUT_ENTRY = 500;
|
||||
private static final int TIMEOUT_OPERATION_LONG = 60000;
|
||||
private static final int TIMEOUT_OPERATION_EXPAND = 120000;
|
||||
|
||||
private String base;
|
||||
private ResourceAddress resourceAddress;
|
||||
private ResourceFormat preferredResourceFormat;
|
||||
private int maxResultSetSize = -1;//_count
|
||||
private CapabilityStatement capabilities;
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
//Pass endpoint for client - URI
|
||||
public FHIRToolingClient(String baseServiceUrl) throws URISyntaxException {
|
||||
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
|
||||
initialize(baseServiceUrl);
|
||||
}
|
||||
|
||||
public void initialize(String baseServiceUrl) throws URISyntaxException {
|
||||
base = baseServiceUrl;
|
||||
resourceAddress = new ResourceAddress(baseServiceUrl);
|
||||
this.maxResultSetSize = -1;
|
||||
checkCapabilities();
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private void checkCapabilities() {
|
||||
try {
|
||||
capabilities = getCapabilitiesStatementQuick();
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getPreferredResourceFormat() {
|
||||
return preferredResourceFormat.getHeader();
|
||||
}
|
||||
|
||||
public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
|
||||
preferredResourceFormat = resourceFormat;
|
||||
}
|
||||
|
||||
public int getMaximumRecordCount() {
|
||||
return maxResultSetSize;
|
||||
}
|
||||
|
||||
public void setMaximumRecordCount(int maxResultSetSize) {
|
||||
this.maxResultSetSize = maxResultSetSize;
|
||||
}
|
||||
|
||||
public TerminologyCapabilities getTerminologyCapabilities() {
|
||||
TerminologyCapabilities capabilities = null;
|
||||
try {
|
||||
capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"TerminologyCapabilities",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's terminology capabilities", e);
|
||||
}
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public CapabilityStatement getCapabilitiesStatement() {
|
||||
CapabilityStatement conformance = null;
|
||||
try {
|
||||
conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CapabilitiesStatement",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's conformance statement", e);
|
||||
}
|
||||
return conformance;
|
||||
}
|
||||
|
||||
public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException {
|
||||
if (capabilities != null) return capabilities;
|
||||
try {
|
||||
capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CapabilitiesStatement-Quick",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's capability statement: "+e.getMessage(), e);
|
||||
}
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Read " + resourceClass.getName() + "/" + id,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
return result.getPayload();
|
||||
}
|
||||
|
||||
public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error trying to read this version of the resource", e);
|
||||
}
|
||||
return result.getPayload();
|
||||
}
|
||||
|
||||
public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("An error has occurred while trying to read this version of the resource", e);
|
||||
}
|
||||
Bundle bnd = (Bundle) result.getPayload();
|
||||
if (bnd.getEntry().size() == 0)
|
||||
throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'");
|
||||
if (bnd.getEntry().size() > 1)
|
||||
throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'");
|
||||
return (T) bnd.getEntry().get(0).getResource();
|
||||
}
|
||||
|
||||
public Resource update(Resource resource) {
|
||||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
|
||||
}
|
||||
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
|
||||
try {
|
||||
OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
|
||||
ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
|
||||
return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
|
||||
} catch (ClassCastException e) {
|
||||
// if we fall throught we have the correct type already in the create
|
||||
}
|
||||
|
||||
return result.getPayload();
|
||||
}
|
||||
|
||||
public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Update " + resource.fhirType() + "/" + id,
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
|
||||
}
|
||||
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
|
||||
try {
|
||||
OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
|
||||
ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
|
||||
return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
|
||||
} catch (ClassCastException e) {
|
||||
// if we fall through we have the correct type already in the create
|
||||
}
|
||||
|
||||
return result.getPayload();
|
||||
}
|
||||
|
||||
public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
|
||||
boolean complex = false;
|
||||
for (ParametersParameterComponent p : params.getParameter())
|
||||
complex = complex || !(p.getValue() instanceof PrimitiveType);
|
||||
String ps = "";
|
||||
try {
|
||||
if (!complex)
|
||||
for (ParametersParameterComponent p : params.getParameter())
|
||||
if (p.getValue() instanceof PrimitiveType)
|
||||
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
|
||||
ResourceRequest<T> result;
|
||||
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
|
||||
if (complex) {
|
||||
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
|
||||
client.getLogger().logRequest("POST", url.toString(), null, body);
|
||||
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
|
||||
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
|
||||
} else {
|
||||
client.getLogger().logRequest("GET", url.toString(), null, null);
|
||||
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
if (result.getPayload() instanceof Parameters) {
|
||||
return (Parameters) result.getPayload();
|
||||
} else {
|
||||
Parameters p_out = new Parameters();
|
||||
p_out.addParameter().setName("return").setResource(result.getPayload());
|
||||
return p_out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Bundle transaction(Bundle batch) {
|
||||
Bundle transactionResult = null;
|
||||
try {
|
||||
transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "transaction", TIMEOUT_OPERATION + (TIMEOUT_ENTRY * batch.getEntry().size()));
|
||||
} catch (Exception e) {
|
||||
handleException("An error occurred trying to process this transaction request", e);
|
||||
}
|
||||
return transactionResult;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(), generateHeaders(),
|
||||
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", TIMEOUT_OPERATION_LONG);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("An error has occurred while trying to validate this resource", e);
|
||||
}
|
||||
return (OperationOutcome) result.getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
*/
|
||||
protected void handleException(String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
throw (EFhirClientException) e;
|
||||
} else {
|
||||
throw new EFhirClientException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine whether desired resource representation
|
||||
* is Json or XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
if (format.toLowerCase().contains("json")) {
|
||||
isJson = true;
|
||||
}
|
||||
return isJson;
|
||||
}
|
||||
|
||||
public Bundle fetchFeed(String url) {
|
||||
Bundle feed = null;
|
||||
try {
|
||||
feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
|
||||
} catch (Exception e) {
|
||||
handleException("An error has occurred while trying to retrieve history since last update", e);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
public ValueSet expandValueset(ValueSet source, Parameters expParams) {
|
||||
Parameters p = expParams == null ? new Parameters() : expParams.copy();
|
||||
p.addParameter().setName("valueSet").setResource(source);
|
||||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"ValueSet/$expand?url=" + source.getUrl(),
|
||||
TIMEOUT_OPERATION_EXPAND);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result == null ? null : (ValueSet) result.getPayload();
|
||||
}
|
||||
|
||||
|
||||
public Parameters lookupCode(Map<String, String> params) {
|
||||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CodeSystem/$lookup",
|
||||
TIMEOUT_NORMAL);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
return (Parameters) result.getPayload();
|
||||
}
|
||||
|
||||
public ValueSet expandValueset(ValueSet source, Parameters expParams, Map<String, String> params) {
|
||||
Parameters p = expParams == null ? new Parameters() : expParams.copy();
|
||||
p.addParameter().setName("valueSet").setResource(source);
|
||||
for (String n : params.keySet()) {
|
||||
p.addParameter().setName(n).setValue(new StringType(params.get(n)));
|
||||
}
|
||||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"ValueSet/$expand?url=" + source.getUrl(),
|
||||
TIMEOUT_OPERATION_EXPAND);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result == null ? null : (ValueSet) result.getPayload();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public ConceptMap initializeClosure(String name) {
|
||||
Parameters params = new Parameters();
|
||||
params.addParameter().setName("name").setValue(new StringType(name));
|
||||
ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Closure?name=" + name,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public ConceptMap updateClosure(String name, Coding coding) {
|
||||
Parameters params = new Parameters();
|
||||
params.addParameter().setName("name").setValue(new StringType(name));
|
||||
params.addParameter().setName("concept").setValue(coding);
|
||||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"UpdateClosure?name=" + name,
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return client.getTimeout();
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
client.setTimeout(timeout);
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return client.getLogger();
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
client.setLogger(logger);
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return client.getRetryCount();
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
private Headers generateHeaders() {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
// Add any other headers
|
||||
if(this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,8 @@ public class ExpressionNode {
|
|||
Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, Power, Truncate,
|
||||
|
||||
// R3 functions
|
||||
Encode, Decode, Escape, Unescape, Trim, Split, Join,
|
||||
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
|
||||
|
||||
// Local extensions to FHIRPath
|
||||
HtmlChecks1, HtmlChecks2, AliasAs, Alias;
|
||||
|
||||
|
@ -151,9 +152,14 @@ public class ExpressionNode {
|
|||
if (name.equals("ln")) return Function.Ln;
|
||||
if (name.equals("log")) return Function.Log;
|
||||
if (name.equals("power")) return Function.Power;
|
||||
if (name.equals("truncate")) return Function.Truncate;
|
||||
if (name.equals("truncate")) return Function.Truncate;
|
||||
if (name.equals("lowBoundary")) return Function.LowBoundary;
|
||||
if (name.equals("highBoundary")) return Function.HighBoundary;
|
||||
if (name.equals("precision")) return Function.Precision;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toCode() {
|
||||
switch (this) {
|
||||
case Empty : return "empty";
|
||||
|
@ -249,7 +255,9 @@ public class ExpressionNode {
|
|||
case Log : return "log";
|
||||
case Power : return "power";
|
||||
case Truncate: return "truncate";
|
||||
|
||||
case LowBoundary: return "lowBoundary";
|
||||
case HighBoundary: return "highBoundary";
|
||||
case Precision: return "precision";
|
||||
default: return "?custom?";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,4 +144,9 @@ public class TimeType extends PrimitiveType<String> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fpValue() {
|
||||
return "@T"+primitiveValue();
|
||||
}
|
||||
|
||||
}
|
|
@ -298,7 +298,7 @@ public class TypeDetails {
|
|||
String t = ProfiledType.ns(n);
|
||||
if (typesContains(t))
|
||||
return true;
|
||||
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
|
||||
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
|
||||
t = FP_NS+Utilities.capitalize(n);
|
||||
if (typesContains(t))
|
||||
return true;
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
|
|||
import org.hl7.fhir.r5.model.ExpressionNode.Function;
|
||||
import org.hl7.fhir.r5.model.ExpressionNode.Kind;
|
||||
import org.hl7.fhir.r5.model.ExpressionNode.Operation;
|
||||
import org.hl7.fhir.r5.model.InstantType;
|
||||
import org.hl7.fhir.r5.model.Property.PropertyMatcher;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.Property;
|
||||
|
@ -125,7 +126,7 @@ public class FHIRPathEngine {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return "%constant";
|
||||
}
|
||||
|
@ -1381,6 +1382,10 @@ public class FHIRPathEngine {
|
|||
case Log: return checkParamCount(lexer, location, exp, 1);
|
||||
case Power: return checkParamCount(lexer, location, exp, 1);
|
||||
case Truncate: return checkParamCount(lexer, location, exp, 0);
|
||||
case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1);
|
||||
case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1);
|
||||
case Precision: return checkParamCount(lexer, location, exp, 0);
|
||||
|
||||
case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
|
||||
}
|
||||
return false;
|
||||
|
@ -3337,6 +3342,25 @@ public class FHIRPathEngine {
|
|||
return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());
|
||||
}
|
||||
|
||||
case LowBoundary:
|
||||
case HighBoundary: {
|
||||
checkContextContinuous(focus, exp.getFunction().toCode(), exp);
|
||||
if (paramTypes.size() > 0) {
|
||||
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
|
||||
}
|
||||
if (focus.hasType("decimal") && (focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);
|
||||
} else if (focus.hasType("decimal")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
|
||||
} else {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
|
||||
}
|
||||
}
|
||||
case Precision: {
|
||||
checkContextContinuous(focus, exp.getFunction().toCode(), exp);
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
|
||||
}
|
||||
|
||||
case Custom : {
|
||||
return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
|
||||
}
|
||||
|
@ -3423,6 +3447,12 @@ public class FHIRPathEngine {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
|
||||
if (!focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe());
|
||||
}
|
||||
}
|
||||
|
||||
private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
|
||||
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
|
||||
for (String f : focus.getTypes()) {
|
||||
|
@ -3534,6 +3564,10 @@ public class FHIRPathEngine {
|
|||
case Log : return funcLog(context, focus, exp);
|
||||
case Power : return funcPower(context, focus, exp);
|
||||
case Truncate : return funcTruncate(context, focus, exp);
|
||||
case LowBoundary : return funcLowBoundary(context, focus, exp);
|
||||
case HighBoundary : return funcHighBoundary(context, focus, exp);
|
||||
case Precision : return funcPrecision(context, focus, exp);
|
||||
|
||||
|
||||
case Custom: {
|
||||
List<List<Base>> params = new ArrayList<List<Base>>();
|
||||
|
@ -3739,6 +3773,84 @@ public class FHIRPathEngine {
|
|||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "lowBoundary", focus.size());
|
||||
}
|
||||
int precision = 0;
|
||||
if (expr.getParameters().size() > 0) {
|
||||
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
|
||||
if (n1.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
|
||||
}
|
||||
precision = Integer.parseInt(n1.get(0).primitiveValue());
|
||||
}
|
||||
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision)));
|
||||
} else if (base.hasType("date")) {
|
||||
result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision)));
|
||||
} else if (base.hasType("dateTime")) {
|
||||
result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "highBoundary", focus.size());
|
||||
}
|
||||
int precision = 0;
|
||||
if (expr.getParameters().size() > 0) {
|
||||
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
|
||||
if (n1.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
|
||||
}
|
||||
precision = Integer.parseInt(n1.get(0).primitiveValue());
|
||||
}
|
||||
|
||||
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision)));
|
||||
} else if (base.hasType("date")) {
|
||||
result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision)));
|
||||
} else if (base.hasType("dateTime")) {
|
||||
result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "highBoundary", focus.size());
|
||||
}
|
||||
Base base = focus.get(0);
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
if (base.hasType("decimal")) {
|
||||
result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue())));
|
||||
} else if (base.hasType("date") || base.hasType("dateTime")) {
|
||||
result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue())));
|
||||
} else if (base.hasType("time")) {
|
||||
result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue())));
|
||||
} else {
|
||||
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
|
||||
if (focus.size() != 1) {
|
||||
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "round", focus.size());
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.nio.file.StandardCopyOption;
|
|||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
@ -1505,5 +1506,229 @@ public class Utilities {
|
|||
return "['"+String.join("' | '", expected)+"']";
|
||||
}
|
||||
|
||||
public static String lowBoundaryForDecimal(String value, int precision) {
|
||||
if (Utilities.noString(value)) {
|
||||
throw new FHIRException("Unable to calculate lowBoundary for a null decimal string");
|
||||
}
|
||||
String e = value.contains("e") ? value.substring(value.indexOf("e")+1) : null;
|
||||
if (value.contains("e")) {
|
||||
value = value.substring(0, value.indexOf("e"));
|
||||
}
|
||||
if (isZero(value)) {
|
||||
return applyPrecision("-0.5000000000000000000000000", precision);
|
||||
} else if (value.startsWith("-")) {
|
||||
return "-"+highBoundaryForDecimal(value.substring(1), precision)+(e == null ? "" : e);
|
||||
} else {
|
||||
if (value.contains(".")) {
|
||||
return applyPrecision(minusOne(value)+"50000000000000000000000000000", precision)+(e == null ? "" : e);
|
||||
} else {
|
||||
return applyPrecision(minusOne(value)+".50000000000000000000000000000", precision)+(e == null ? "" : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String applyPrecision(String v, int p) {
|
||||
int d = p - getDecimalPrecision(v);
|
||||
if (d == 0) {
|
||||
return v;
|
||||
} else if (d > 0) {
|
||||
return v + padLeft("", '0', d);
|
||||
} else {
|
||||
if (v.charAt(v.length()+d) >= '6') {
|
||||
return v.substring(0, v.length()+d-1)+((char) (v.charAt(v.length()+d)+1));
|
||||
} else {
|
||||
return v.substring(0, v.length()+d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String minusOne(String value) {
|
||||
StringBuffer s = new StringBuffer(value);
|
||||
for (int i = s.length()-1; i >= 0; i--) {
|
||||
if (s.charAt(i) == '0') {
|
||||
s.setCharAt(i, '9');
|
||||
} else if (s.charAt(i) != '.') {
|
||||
s.setCharAt(i, (char) (s.charAt(i)-1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static String lowBoundaryForDate(String value, int precision) {
|
||||
String[] res = splitTimezone(value);
|
||||
StringBuilder b = new StringBuilder(res[0]);
|
||||
if (b.length() == 4) {
|
||||
b.append("-01");
|
||||
}
|
||||
if (b.length() == 7) {
|
||||
b.append("-01");
|
||||
}
|
||||
if (b.length() == 10) {
|
||||
b.append("T00:00");
|
||||
}
|
||||
if (b.length() == 16) {
|
||||
b.append(":00");
|
||||
}
|
||||
if (b.length() == 19) {
|
||||
b.append(".000");
|
||||
}
|
||||
return applyDatePrecision(b.toString(), precision)+res[1];
|
||||
}
|
||||
|
||||
public static String lowBoundaryForTime(String value, int precision) {
|
||||
String[] res = splitTimezone(value);
|
||||
StringBuilder b = new StringBuilder(res[0]);
|
||||
if (b.length() == 2) {
|
||||
b.append(":00");
|
||||
}
|
||||
if (b.length() == 5) {
|
||||
b.append(":00");
|
||||
}
|
||||
if (b.length() == 8) {
|
||||
b.append(".000");
|
||||
}
|
||||
return applyTimePrecision(b.toString(), precision)+res[1];
|
||||
}
|
||||
|
||||
public static String highBoundaryForTime(String value, int precision) {
|
||||
String[] res = splitTimezone(value);
|
||||
StringBuilder b = new StringBuilder(res[0]);
|
||||
if (b.length() == 2) {
|
||||
b.append(":59");
|
||||
}
|
||||
if (b.length() == 5) {
|
||||
b.append(":59");
|
||||
}
|
||||
if (b.length() == 8) {
|
||||
b.append(".999");
|
||||
}
|
||||
return applyTimePrecision(b.toString(), precision)+res[1];
|
||||
}
|
||||
|
||||
|
||||
private static Object applyDatePrecision(String v, int precision) {
|
||||
switch (precision) {
|
||||
case 4: return v.substring(0, 4);
|
||||
case 6: return v.substring(0, 7);
|
||||
case 8: return v.substring(0, 10);
|
||||
case 14: return v.substring(0, 17);
|
||||
case 17: return v;
|
||||
}
|
||||
throw new FHIRException("Unsupported Date precision for boundary operation: "+precision);
|
||||
}
|
||||
|
||||
private static Object applyTimePrecision(String v, int precision) {
|
||||
switch (precision) {
|
||||
case 2: return v.substring(0, 3);
|
||||
case 4: return v.substring(0, 6);
|
||||
case 6: return v.substring(0, 9);
|
||||
case 9: return v;
|
||||
}
|
||||
throw new FHIRException("Unsupported Time precision for boundary operation: "+precision);
|
||||
}
|
||||
|
||||
public static String highBoundaryForDecimal(String value, int precision) {
|
||||
if (Utilities.noString(value)) {
|
||||
throw new FHIRException("Unable to calculate highBoundary for a null decimal string");
|
||||
}
|
||||
String e = value.contains("e") ? value.substring(value.indexOf("e")+1) : null;
|
||||
if (value.contains("e")) {
|
||||
value = value.substring(0, value.indexOf("e"));
|
||||
}
|
||||
if (isZero(value)) {
|
||||
return applyPrecision("0.50000000000000000000000000000", precision);
|
||||
} else if (value.startsWith("-")) {
|
||||
return "-"+lowBoundaryForDecimal(value.substring(1), precision)+(e == null ? "" : e);
|
||||
} else {
|
||||
if (value.contains(".")) {
|
||||
return applyPrecision(value+"50000000000000000000000000000", precision)+(e == null ? "" : e);
|
||||
} else {
|
||||
return applyPrecision(value+".50000000000000000000000000000", precision)+(e == null ? "" : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isZero(String value) {
|
||||
return value.replace(".", "").replace("-", "").replace("0", "").length() == 0;
|
||||
}
|
||||
|
||||
public static String highBoundaryForDate(String value, int precision) {
|
||||
String[] res = splitTimezone(value);
|
||||
StringBuilder b = new StringBuilder(res[0]);
|
||||
if (b.length() == 4) {
|
||||
b.append("-12");
|
||||
}
|
||||
if (b.length() == 7) {
|
||||
b.append("-"+dayCount(Integer.parseInt(b.substring(0,4)), Integer.parseInt(b.substring(5,7))));
|
||||
}
|
||||
if (b.length() == 10) {
|
||||
b.append("T23:59");
|
||||
}
|
||||
if (b.length() == 16) {
|
||||
b.append(":59");
|
||||
}
|
||||
if (b.length() == 19) {
|
||||
b.append(".999");
|
||||
}
|
||||
return applyDatePrecision(b.toString(), precision)+res[1];
|
||||
}
|
||||
|
||||
private static String dayCount(int y, int m) {
|
||||
switch (m) {
|
||||
case 1: return "31";
|
||||
case 2: return ((y % 4 == 0) && (y % 400 == 0 || !(y % 100 == 0))) ? "29" : "28";
|
||||
case 3: return "31";
|
||||
case 4: return "30";
|
||||
case 5: return "31";
|
||||
case 6: return "30";
|
||||
case 7: return "31";
|
||||
case 8: return "31";
|
||||
case 9: return "30";
|
||||
case 10: return "31";
|
||||
case 11: return "30";
|
||||
case 12: return "31";
|
||||
default: return "30"; // make the compiler happy
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getDecimalPrecision(String value) {
|
||||
if (value.contains("e")) {
|
||||
value = value.substring(0, value.indexOf("e"));
|
||||
}
|
||||
if (value.contains(".")) {
|
||||
return value.split("\\.")[1].length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String[] splitTimezone(String value) {
|
||||
String[] res = new String[2];
|
||||
|
||||
if (value.contains("+")) {
|
||||
res[0] = value.substring(0, value.indexOf("+"));
|
||||
res[1] = value.substring(value.indexOf("+"));
|
||||
} else if (value.contains("-") && value.contains("T") && value.lastIndexOf("-") > value.indexOf("T")) {
|
||||
res[0] = value.substring(0, value.lastIndexOf("-"));
|
||||
res[1] = value.substring(value.lastIndexOf("-"));
|
||||
} else if (value.contains("Z")) {
|
||||
res[0] = value.substring(0, value.indexOf("Z"));
|
||||
res[1] = value.substring(value.indexOf("Z"));
|
||||
} else {
|
||||
res[0] = value;
|
||||
res[1] = "";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Integer getDatePrecision(String value) {
|
||||
return splitTimezone(value)[0].replace("-", "").replace("T", "").replace(":", "").replace(".", "").length();
|
||||
}
|
||||
|
||||
public static Integer getTimePrecision(String value) {
|
||||
return splitTimezone(value)[0].replace("T", "").replace(":", "").replace(".", "").length();
|
||||
}
|
||||
|
||||
}
|
|
@ -138,6 +138,7 @@ public class I18nConstants {
|
|||
public static final String FHIRPATH_CHECK_FAILED = "FHIRPATH_CHECK_FAILED";
|
||||
public static final String FHIRPATH_CODED_ONLY = "FHIRPATH_CODED_ONLY";
|
||||
public static final String FHIRPATH_DECIMAL_ONLY = "FHIRPATH_DECIMAL_ONLY";
|
||||
public static final String FHIRPATH_CONTINUOUS_ONLY = "FHIRPATH_CONTINUOUS_ONLY";
|
||||
public static final String FHIRPATH_DISCRIMINATOR_BAD_NAME = "FHIRPATH_DISCRIMINATOR_BAD_NAME";
|
||||
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST";
|
||||
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP";
|
||||
|
|
|
@ -617,6 +617,7 @@ TERMINOLOGY_TX_SYSTEM_WRONG_BUILD = The code system reference {0} is wrong - th
|
|||
FHIRPATH_BAD_DATE = Unable to parse Date {0}
|
||||
FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on integer, decimal or Quantity but found {1}
|
||||
FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1}
|
||||
FHIRPATH_CONTINUOUS_ONLY= Error evaluating FHIRPath expression: The function {0} can only be used on a decimal or date type but found {1}
|
||||
FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value
|
||||
REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, and it should be checked
|
||||
TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable
|
||||
|
|
|
@ -167,5 +167,67 @@ class UtilitiesTest {
|
|||
);
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> Utilities.describeSize(BIG_NEG));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Decimal Reasoning Tests")
|
||||
void testDecimalRoutines() {
|
||||
Assertions.assertEquals("-0.500000", Utilities.lowBoundaryForDecimal("0", 6));
|
||||
Assertions.assertEquals("0.50000000", Utilities.lowBoundaryForDecimal("1", 8));
|
||||
Assertions.assertEquals("0.950000", Utilities.lowBoundaryForDecimal("1.0", 6));
|
||||
Assertions.assertEquals("0.95", Utilities.lowBoundaryForDecimal("1.0", 2));
|
||||
Assertions.assertEquals("-1.05000000", Utilities.lowBoundaryForDecimal("-1.0", 8));
|
||||
Assertions.assertEquals("1.23", Utilities.lowBoundaryForDecimal("1.234", 2));
|
||||
Assertions.assertEquals("1.57", Utilities.lowBoundaryForDecimal("1.567", 2));
|
||||
|
||||
Assertions.assertEquals("0.50000000", Utilities.highBoundaryForDecimal("0", 8));
|
||||
Assertions.assertEquals("1.500000", Utilities.highBoundaryForDecimal("1", 6));
|
||||
Assertions.assertEquals("1.0500000000", Utilities.highBoundaryForDecimal("1.0", 10));
|
||||
Assertions.assertEquals("-0.9500", Utilities.highBoundaryForDecimal("-1.0", 4));
|
||||
|
||||
Assertions.assertEquals(0, Utilities.getDecimalPrecision("0"));
|
||||
Assertions.assertEquals(0, Utilities.getDecimalPrecision("1"));
|
||||
Assertions.assertEquals(1, Utilities.getDecimalPrecision("1.0"));
|
||||
Assertions.assertEquals(1, Utilities.getDecimalPrecision("-1.0"));
|
||||
Assertions.assertEquals(4, Utilities.getDecimalPrecision("-1.0200"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Date Reasoning Tests")
|
||||
void testDateRoutines() {
|
||||
// Assertions.assertEquals("2021-01-01T00:00:00.000", Utilities.lowBoundaryForDate("2021"));
|
||||
// Assertions.assertEquals("2021-04-01T00:00:00.000", Utilities.lowBoundaryForDate("2021-04"));
|
||||
// Assertions.assertEquals("2020-02-01T00:00:00.000", Utilities.lowBoundaryForDate("2020-02"));
|
||||
// Assertions.assertEquals("2021-04-04T00:00:00.000", Utilities.lowBoundaryForDate("2021-04-04"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.000", Utilities.lowBoundaryForDate("2021-04-04T21:22:23"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.245", Utilities.lowBoundaryForDate("2021-04-04T21:22:23.245"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.000Z", Utilities.lowBoundaryForDate("2021-04-04T21:22:23Z"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.245+10:00", Utilities.lowBoundaryForDate("2021-04-04T21:22:23.245+10:00"));
|
||||
//
|
||||
// Assertions.assertEquals("2021-12-31T23:23:59.999", Utilities.highBoundaryForDate("2021"));
|
||||
// Assertions.assertEquals("2021-04-30T23:23:59.999", Utilities.highBoundaryForDate("2021-04"));
|
||||
// Assertions.assertEquals("2020-02-29T23:23:59.999", Utilities.highBoundaryForDate("2020-02"));
|
||||
// Assertions.assertEquals("2021-04-04T23:23:59.999", Utilities.highBoundaryForDate("2021-04-04"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.999", Utilities.highBoundaryForDate("2021-04-04T21:22:23"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.245", Utilities.highBoundaryForDate("2021-04-04T21:22:23.245"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.999Z", Utilities.highBoundaryForDate("2021-04-04T21:22:23Z"));
|
||||
// Assertions.assertEquals("2021-04-04T21:22:23.245+10:00", Utilities.highBoundaryForDate("2021-04-04T21:22:23.245+10:00"));
|
||||
|
||||
Assertions.assertEquals(8, Utilities.getDatePrecision("1900-01-01"));
|
||||
Assertions.assertEquals(4, Utilities.getDatePrecision("1900"));
|
||||
Assertions.assertEquals(6, Utilities.getDatePrecision("1900-06"));
|
||||
Assertions.assertEquals(14, Utilities.getDatePrecision("1900-06-06T14:00:00"));
|
||||
Assertions.assertEquals(17, Utilities.getDatePrecision("1900-06-06T14:00:00.000"));
|
||||
Assertions.assertEquals(8, Utilities.getDatePrecision("1900-01-01Z"));
|
||||
Assertions.assertEquals(4, Utilities.getDatePrecision("1900Z"));
|
||||
Assertions.assertEquals(6, Utilities.getDatePrecision("1900-06Z"));
|
||||
Assertions.assertEquals(14, Utilities.getDatePrecision("1900-06-06T14:00:00Z"));
|
||||
Assertions.assertEquals(17, Utilities.getDatePrecision("1900-06-06T14:00:00.000Z"));
|
||||
Assertions.assertEquals(8, Utilities.getDatePrecision("1900-01-01+10:00"));
|
||||
Assertions.assertEquals(4, Utilities.getDatePrecision("1900+10:00"));
|
||||
Assertions.assertEquals(6, Utilities.getDatePrecision("1900-06+10:00"));
|
||||
Assertions.assertEquals(14, Utilities.getDatePrecision("1900-06-06T14:00:00+10:00"));
|
||||
Assertions.assertEquals(17, Utilities.getDatePrecision("1900-06-06T14:00:00.000-10:00"));
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue