Credit for #1065 and forward-port fix to R4 provider

This commit is contained in:
jamesagnew 2019-01-09 07:19:55 -05:00
parent f8b232bb67
commit b3c9b32db4
9 changed files with 15576 additions and 13793 deletions

View File

@ -28,6 +28,7 @@ import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.OperationDefinition.OperationDefinitionParameterComponent;
import org.hl7.fhir.r4.model.OperationDefinition.OperationKind;
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
import org.hl7.fhir.r4.model.codesystems.UnknownContentCode;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@ -71,13 +72,15 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
private boolean myCache = true;
private volatile CapabilityStatement myCapabilityStatement;
private IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName;
private HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings;
private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName;
private HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings;
private String myPublisher = "Not provided";
private Callable<RestulfulServerConfiguration> myServerConfiguration;
/**
* No-arg constructor and seetter so that the ServerConfirmanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen.
* No-arg constructor and setter so that the ServerConformanceProvider can be Spring-wired with the RestfulService avoiding the potential reference cycle that would happen.
*/
public ServerCapabilityStatementProvider() {
super();
@ -119,12 +122,12 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
}
private Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() {
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>();
for (ResourceBinding next : getServerConfiguration().getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<>());
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
@ -151,6 +154,17 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
return DateTimeType.now();
}
private String createNamedQueryName(SearchMethodBinding searchMethodBinding) {
StringBuilder retVal = new StringBuilder();
if (searchMethodBinding.getResourceName() != null) {
retVal.append(searchMethodBinding.getResourceName());
}
retVal.append("-query-");
retVal.append(searchMethodBinding.getQueryName());
return retVal.toString();
}
private String createOperationName(OperationMethodBinding theMethodBinding) {
StringBuilder retVal = new StringBuilder();
if (theMethodBinding.getResourceName() != null) {
@ -230,14 +244,15 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
Set<String> operationNames = new HashSet<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet())
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
Set<TypeRestfulInteraction> resourceOps = new HashSet<TypeRestfulInteraction>();
Set<TypeRestfulInteraction> resourceOps = new HashSet<>();
CapabilityStatementRestResourceComponent resource = rest.addResource();
String resourceName = nextEntry.getKey();
RuntimeResourceDefinition def = getServerConfiguration().getFhirContext().getResourceDefinition(resourceName);
resource.getTypeElement().setValue(def.getName());
resource.getProfileElement().setValue(def.getResourceProfile(serverBase));
resource.getProfileElement().setValue((def.getResourceProfile(serverBase)));
TreeSet<String> includes = new TreeSet<>();
@ -293,13 +308,21 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof SearchMethodBinding) {
handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding);
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (methodBinding.getQueryName() != null) {
String queryName = myNamedSearchMethodBindingToName.get(methodBinding);
if (operationNames.add(queryName)) {
rest.addOperation().setName(methodBinding.getQueryName()).setDefinition(("OperationDefinition/" + queryName));
}
} else {
handleNamelessSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding);
}
} else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = myOperationBindingToName.get(methodBinding);
if (operationNames.add(opName)) {
// Only add each operation (by name) once
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition("OperationDefinition/" + opName);
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(("OperationDefinition/" + opName));
}
}
@ -334,19 +357,19 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
String opName = myOperationBindingToName.get(methodBinding);
if (operationNames.add(opName)) {
ourLog.debug("Found bound operation: {}", opName);
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition("OperationDefinition/" + opName);
rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(("OperationDefinition/" + opName));
}
}
}
}
}
myCapabilityStatement = retVal;
return retVal;
}
private void handleSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes,
SearchMethodBinding searchMethodBinding) {
private void handleNamelessSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes,
SearchMethodBinding searchMethodBinding) {
includes.addAll(searchMethodBinding.getIncludes());
List<IParameter> params = searchMethodBinding.getParameters();
@ -434,6 +457,8 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
@Initialize
public void initializeOperations() {
myNamedSearchMethodBindingToName = new IdentityHashMap<>();
mySearchNameToBindings = new HashMap<>();
myOperationBindingToName = new IdentityHashMap<>();
myOperationNameToBindings = new HashMap<>();
@ -452,9 +477,23 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
myOperationBindingToName.put(methodBinding, name);
if (myOperationNameToBindings.containsKey(name) == false) {
myOperationNameToBindings.put(name, new ArrayList<OperationMethodBinding>());
myOperationNameToBindings.put(name, new ArrayList<>());
}
myOperationNameToBindings.get(name).add(methodBinding);
} else if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (myNamedSearchMethodBindingToName.containsKey(methodBinding)) {
continue;
}
String name = createNamedQueryName(methodBinding);
ourLog.debug("Detected named query: {}", name);
myNamedSearchMethodBindingToName.put(methodBinding, name);
if (!mySearchNameToBindings.containsKey(name)) {
mySearchNameToBindings.put(name, new ArrayList<>());
}
mySearchNameToBindings.get(name).add(methodBinding);
}
}
}
@ -465,11 +504,69 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(theId);
}
List<OperationMethodBinding> sharedDescriptions = myOperationNameToBindings.get(theId.getIdPart());
if (sharedDescriptions == null || sharedDescriptions.isEmpty()) {
throw new ResourceNotFoundException(theId);
List<OperationMethodBinding> operationBindings = myOperationNameToBindings.get(theId.getIdPart());
if (operationBindings != null && !operationBindings.isEmpty()) {
return readOperationDefinitionForOperation(operationBindings);
}
List<SearchMethodBinding> searchBindings = mySearchNameToBindings.get(theId.getIdPart());
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(theId);
}
private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.QUERY);
op.setAffectsState(false);
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
for (SearchMethodBinding binding : bindings) {
if (isNotBlank(binding.getDescription())) {
op.setDescription(binding.getDescription());
}
if (isBlank(binding.getResourceProviderResourceName())) {
op.setSystem(true);
} else {
op.setType(true);
op.addResourceElement().setValue(binding.getResourceProviderResourceName());
}
op.setCode(binding.getQueryName());
for (IParameter nextParamUntyped : binding.getParameters()) {
if (nextParamUntyped instanceof SearchParameter) {
SearchParameter nextParam = (SearchParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.IN);
param.setType("string");
param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode());
param.setMin(nextParam.isRequired() ? 1 : 0);
param.setMax("1");
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
}
return op;
}
private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.OPERATION);
@ -480,10 +577,10 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<String>();
Set<String> outParams = new HashSet<String>();
Set<String> inParams = new HashSet<>();
Set<String> outParams = new HashSet<>();
for (OperationMethodBinding sharedDescription : sharedDescriptions) {
for (OperationMethodBinding sharedDescription : bindings) {
if (isNotBlank(sharedDescription.getDescription())) {
op.setDescription(sharedDescription.getDescription());
}
@ -569,8 +666,9 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
* See the class documentation for an important note if you are extending this class
* </p>
*/
public void setCache(boolean theCache) {
public ServerCapabilityStatementProvider setCache(boolean theCache) {
myCache = theCache;
return this;
}
@Override

View File

@ -15,8 +15,6 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine.ExpressionNodeWithOffset;
import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.utilities.Utilities;
import javafx.scene.Parent;
public class LiquidEngine implements IEvaluationContext {
public interface ILiquidEngineIcludeResolver {

View File

@ -0,0 +1,183 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import java.util.Set;
// import ca.uhn.fhir.model.dstu.resource.Binary;
// import ca.uhn.fhir.model.dstu2.resource.Bundle;
// import ca.uhn.fhir.model.api.Bundle;
public class PatientResourceProvider implements IResourceProvider
{
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Search()
public IBundleProvider search(
javax.servlet.http.HttpServletRequest theServletRequest,
@Description(shortDefinition="The resource identity")
@OptionalParam(name="_id")
StringAndListParam theId,
@Description(shortDefinition="The resource language")
@OptionalParam(name="_language")
StringAndListParam theResourceLanguage,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OptionalParam(name=Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OptionalParam(name=Constants.PARAM_TEXT)
StringAndListParam theFtText,
@Description(shortDefinition="Search for resources which have the given tag")
@OptionalParam(name=Constants.PARAM_TAG)
TokenAndListParam theSearchForTag,
@Description(shortDefinition="Search for resources which have the given security labels")
@OptionalParam(name=Constants.PARAM_SECURITY)
TokenAndListParam theSearchForSecurity,
@Description(shortDefinition="Search for resources which have the given profile")
@OptionalParam(name=Constants.PARAM_PROFILE)
UriAndListParam theSearchForProfile,
@Description(shortDefinition="A patient identifier")
@OptionalParam(name="identifier")
TokenAndListParam theIdentifier,
@Description(shortDefinition="A portion of either family or given name of the patient")
@OptionalParam(name="name")
StringAndListParam theName,
@Description(shortDefinition="A portion of the family name of the patient")
@OptionalParam(name="family")
StringAndListParam theFamily,
@Description(shortDefinition="A portion of the given name of the patient")
@OptionalParam(name="given")
StringAndListParam theGiven,
@Description(shortDefinition="A portion of either family or given name using some kind of phonetic matching algorithm")
@OptionalParam(name="phonetic")
StringAndListParam thePhonetic,
@Description(shortDefinition="The value in any kind of telecom details of the patient")
@OptionalParam(name="telecom")
TokenAndListParam theTelecom,
@Description(shortDefinition="A value in a phone contact")
@OptionalParam(name="phone")
TokenAndListParam thePhone,
@Description(shortDefinition="A value in an email contact")
@OptionalParam(name="email")
TokenAndListParam theEmail,
@Description(shortDefinition="An address in any kind of address/part of the patient")
@OptionalParam(name="address")
StringAndListParam theAddress,
@Description(shortDefinition="A city specified in an address")
@OptionalParam(name="address-city")
StringAndListParam theAddress_city,
@Description(shortDefinition="A state specified in an address")
@OptionalParam(name="address-state")
StringAndListParam theAddress_state,
@Description(shortDefinition="A postalCode specified in an address")
@OptionalParam(name="address-postalcode")
StringAndListParam theAddress_postalcode,
@Description(shortDefinition="A country specified in an address")
@OptionalParam(name="address-country")
StringAndListParam theAddress_country,
@Description(shortDefinition="A use code specified in an address")
@OptionalParam(name="address-use")
TokenAndListParam theAddress_use,
@Description(shortDefinition="Gender of the patient")
@OptionalParam(name="gender")
TokenAndListParam theGender,
@Description(shortDefinition="Language code (irrespective of use value)")
@OptionalParam(name="language")
TokenAndListParam theLanguage,
@Description(shortDefinition="The patient's date of birth")
@OptionalParam(name="birthdate")
DateRangeParam theBirthdate,
@Description(shortDefinition="The organization at which this person is a patient")
@OptionalParam(name="organization", targetTypes={ Organization.class } )
ReferenceAndListParam theOrganization,
@Description(shortDefinition="Patient's nominated care provider, could be a care manager, not the organization that manages the record")
@OptionalParam(name="careprovider", targetTypes={ Organization.class , Practitioner.class } )
ReferenceAndListParam theCareprovider,
@Description(shortDefinition="Whether the patient record is active")
@OptionalParam(name="active")
TokenAndListParam theActive,
@Description(shortDefinition="The species for animal patients")
@OptionalParam(name="animal-species")
TokenAndListParam theAnimal_species,
@Description(shortDefinition="The breed for animal patients")
@OptionalParam(name="animal-breed")
TokenAndListParam theAnimal_breed,
@Description(shortDefinition="All patients linked to the given patient")
@OptionalParam(name="link", targetTypes={ Patient.class } )
ReferenceAndListParam theLink,
@Description(shortDefinition="This patient has been marked as deceased, or as a death date entered")
@OptionalParam(name="deceased")
TokenAndListParam theDeceased,
@Description(shortDefinition="The date of death has been provided and satisfies this search value")
@OptionalParam(name="deathdate")
DateRangeParam theDeathdate,
@IncludeParam(reverse=true)
Set<Include> theRevIncludes,
@Description(shortDefinition="Only return resources which were last updated as specified by the given range")
@OptionalParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Patient:careprovider" , "Patient:link" , "Patient:organization" , "Patient:careprovider" , "Patient:link" , "Patient:organization" , "Patient:careprovider" , "Patient:link" , "Patient:organization" , "*"
})
Set<Include> theIncludes,
@Sort
SortSpec theSort,
@Count
Integer theCount
) {
return null;
}
}

View File

@ -1,90 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
<sch:ns prefix="f" uri="http://hl7.org/fhir"/>
<sch:ns prefix="h" uri="http://www.w3.org/1999/xhtml"/>
<!--
This file contains just the constraints for the resource CapabilityStatement
It is provided for documentation purposes. When actually validating,
always use fhir-invariants.sch (because of the way containment works)
Alternatively you can use this file to build a smaller version of
fhir-invariants.sch (the contents are identical; only include those
resources relevant to your implementation).
-->
<sch:pattern>
<sch:title>Global</sch:title>
<sch:rule context="f:extension">
<sch:assert test="exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])">ext-1: Must have either extensions or value[x], not both</sch:assert>
</sch:rule>
<sch:rule context="f:modifierExtension">
<sch:assert test="exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])">ext-1: Must have either extensions or value[x], not both</sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern>
<sch:title>Global 1</sch:title>
<sch:rule context="f:*">
<sch:assert test="@value|f:*|h:div">global-1: All FHIR elements must have a @value or children</sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern>
<sch:title>CapabilityStatement</sch:title>
<sch:rule context="f:CapabilityStatement">
<sch:assert test="not(parent::f:contained and f:contained)">dom-2: If the resource is contained in another resource, it SHALL NOT contain nested Resources</sch:assert>
<sch:assert test="not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))">dom-4: If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated</sch:assert>
<sch:assert test="not(exists(for $contained in f:contained return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))">dom-3: If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource</sch:assert>
<sch:assert test="not(exists(f:contained/*/f:meta/f:security))">dom-5: If a resource is contained in another resource, it SHALL NOT have a security label</sch:assert>
<sch:assert test="count(f:document[f:mode/@value='producer'])=count(distinct-values(f:document[f:mode/@value='producer']/f:profile/f:reference/@value)) and count(f:document[f:mode/@value='consumer'])=count(distinct-values(f:document[f:mode/@value='consumer']/f:profile/f:reference/@value))">cpb-7: The set of documents must be unique by the combination of profile and mode.</sch:assert>
<sch:assert test="not(f:kind/@value='instance') or (not(exists(f:implementation)) and not(exists(f:software)))">cpb-16: If kind = requirements, implementation and software must be absent</sch:assert>
<sch:assert test=" not(f:kind/@value='instance') or (not(exists(f:implementation)) and exists(f:software))">cpb-15: If kind = capability, implementation must be absent, software must be present</sch:assert>
<sch:assert test="not(exists(f:messaging/f:endpoint)) or f:kind/@value = 'instance'">cpb-3: Messaging end-point is required (and is only permitted) when a statement is for an implementation.</sch:assert>
<sch:assert test="not(f:kind/@value='instance') or exists(f:implementation)">cpb-14: If kind = instance, implementation must be present and software may be present</sch:assert>
<sch:assert test="count(f:software | f:implementation | f:description) &gt; 0">cpb-2: A Capability Statement SHALL have at least one of description, software, or implementation element.</sch:assert>
<sch:assert test="exists(f:rest) or exists(f:messaging) or exists(f:document)">cpb-1: A Capability Statement SHALL have at least one of REST, messaging or document element.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:text/h:div">
<sch:assert test="not(descendant-or-self::*[not(local-name(.)=('a', 'abbr', 'acronym', 'b', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))]) and not(descendant-or-self::*/@*[not(name(.)=('abbr', 'accesskey', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellhalign', 'cellpadding', 'cellspacing', 'cellvalign', 'char', 'charoff', 'charset', 'cite', 'class', 'colspan', 'compact', 'coords', 'dir', 'frame', 'headers', 'height', 'href', 'hreflang', 'hspace', 'id', 'lang', 'longdesc', 'name', 'nowrap', 'rel', 'rev', 'rowspan', 'rules', 'scope', 'shape', 'span', 'src', 'start', 'style', 'summary', 'tabindex', 'title', 'type', 'valign', 'value', 'vspace', 'width'))])">txt-1: The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, &lt;a&gt; elements (either name or href), images and internally contained style attributes</sch:assert>
<sch:assert test="descendant::text()[normalize-space(.)!=''] or descendant::h:img[@src]">txt-2: The narrative SHALL have some non-whitespace content</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:contact/f:telecom">
<sch:assert test="not(exists(f:value)) or exists(f:system)">cpt-2: A system is required if a value is provided.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:contact/f:telecom/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueQuantity">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange">
<sch:assert test="not(exists(f:low/f:value/@value)) or not(exists(f:high/f:value/@value)) or (number(f:low/f:value/@value) &lt;= number(f:high/f:value/@value))">rng-2: If present, low SHALL have a lower value than high</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange/f:low">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange/f:high">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference/f:identifier/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference/f:identifier/f:assigner">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian/f:identifier/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian/f:identifier//f:assigner">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:rest">
<sch:assert test="count(f:resource)=count(distinct-values(f:resource/f:type/@value))">cpb-9: A given resource can only be described once per RESTful mode.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:rest/f:resource">
<sch:assert test="count(f:searchParam)=count(distinct-values(f:searchParam/f:name/@value))">cpb-12: Search parameter names must be unique in the context of a resource.</sch:assert>
</sch:rule>
</sch:pattern>
</sch:schema>
<?xml version="1.0" encoding="UTF-8"?>
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
<sch:ns prefix="f" uri="http://hl7.org/fhir"/>
<sch:ns prefix="h" uri="http://www.w3.org/1999/xhtml"/>
<!--
This file contains just the constraints for the resource CapabilityStatement
It is provided for documentation purposes. When actually validating,
always use fhir-invariants.sch (because of the way containment works)
Alternatively you can use this file to build a smaller version of
fhir-invariants.sch (the contents are identical; only include those
resources relevant to your implementation).
-->
<sch:pattern>
<sch:title>Global</sch:title>
<sch:rule context="f:extension">
<sch:assert test="exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])">ext-1: Must have either extensions or value[x], not both</sch:assert>
</sch:rule>
<sch:rule context="f:modifierExtension">
<sch:assert test="exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])">ext-1: Must have either extensions or value[x], not both</sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern>
<sch:title>Global 1</sch:title>
<sch:rule context="f:*">
<sch:assert test="@value|f:*|h:div">global-1: All FHIR elements must have a @value or children</sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern>
<sch:title>CapabilityStatement</sch:title>
<sch:rule context="f:CapabilityStatement">
<sch:assert test="not(parent::f:contained and f:contained)">dom-2: If the resource is contained in another resource, it SHALL NOT contain nested Resources</sch:assert>
<sch:assert test="not(exists(f:contained/*/f:meta/f:versionId)) and not(exists(f:contained/*/f:meta/f:lastUpdated))">dom-4: If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated</sch:assert>
<sch:assert test="not(exists(for $contained in f:contained return $contained[not(parent::*/descendant::f:reference/@value=concat('#', $contained/*/id/@value) or descendant::f:reference[@value='#'])]))">dom-3: If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource</sch:assert>
<sch:assert test="not(exists(f:contained/*/f:meta/f:security))">dom-5: If a resource is contained in another resource, it SHALL NOT have a security label</sch:assert>
<sch:assert test="count(f:document[f:mode/@value='producer'])=count(distinct-values(f:document[f:mode/@value='producer']/f:profile/f:reference/@value)) and count(f:document[f:mode/@value='consumer'])=count(distinct-values(f:document[f:mode/@value='consumer']/f:profile/f:reference/@value))">cpb-7: The set of documents must be unique by the combination of profile and mode.</sch:assert>
<sch:assert test="not(f:kind/@value='requirements') or (not(exists(f:implementation)) and not(exists(f:software)))">cpb-16: If kind = requirements, implementation and software must be absent</sch:assert>
<sch:assert test=" not(f:kind/@value='capability') or (not(exists(f:implementation)) and exists(f:software))">cpb-15: If kind = capability, implementation must be absent, software must be present</sch:assert>
<sch:assert test="not(exists(f:messaging/f:endpoint)) or f:kind/@value = 'instance'">cpb-3: Messaging end-point is required (and is only permitted) when a statement is for an implementation.</sch:assert>
<sch:assert test="not(f:kind/@value='instance') or exists(f:implementation)">cpb-14: If kind = instance, implementation must be present and software may be present</sch:assert>
<sch:assert test="count(f:software | f:implementation | f:description) &gt; 0">cpb-2: A Capability Statement SHALL have at least one of description, software, or implementation element.</sch:assert>
<sch:assert test="exists(f:rest) or exists(f:messaging) or exists(f:document)">cpb-1: A Capability Statement SHALL have at least one of REST, messaging or document element.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:text/h:div">
<sch:assert test="not(descendant-or-self::*[not(local-name(.)=('a', 'abbr', 'acronym', 'b', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))]) and not(descendant-or-self::*/@*[not(name(.)=('abbr', 'accesskey', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellhalign', 'cellpadding', 'cellspacing', 'cellvalign', 'char', 'charoff', 'charset', 'cite', 'class', 'colspan', 'compact', 'coords', 'dir', 'frame', 'headers', 'height', 'href', 'hreflang', 'hspace', 'id', 'lang', 'longdesc', 'name', 'nowrap', 'rel', 'rev', 'rowspan', 'rules', 'scope', 'shape', 'span', 'src', 'start', 'style', 'summary', 'tabindex', 'title', 'type', 'valign', 'value', 'vspace', 'width'))])">txt-1: The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, &lt;a&gt; elements (either name or href), images and internally contained style attributes</sch:assert>
<sch:assert test="descendant::text()[normalize-space(.)!=''] or descendant::h:img[@src]">txt-2: The narrative SHALL have some non-whitespace content</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:contact/f:telecom">
<sch:assert test="not(exists(f:value)) or exists(f:system)">cpt-2: A system is required if a value is provided.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:contact/f:telecom/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueQuantity">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange">
<sch:assert test="not(exists(f:low/f:value/@value)) or not(exists(f:high/f:value/@value)) or (number(f:low/f:value/@value) &lt;= number(f:high/f:value/@value))">rng-2: If present, low SHALL have a lower value than high</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange/f:low">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueRange/f:high">
<sch:assert test="not(exists(f:code)) or exists(f:system)">qty-3: If a code for the unit is present, the system SHALL also be present</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference/f:identifier/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:useContext/f:valueReference/f:identifier/f:assigner">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian/f:identifier/f:period">
<sch:assert test="not(exists(f:start/@value)) or not(exists(f:end/@value)) or (xs:dateTime(f:start/@value) &lt;= xs:dateTime(f:end/@value))">per-1: If present, start SHALL have a lower value than end</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:implementation/f:custodian/f:identifier//f:assigner">
<sch:assert test="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])">ref-1: SHALL have a contained resource if a local reference is provided</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:rest">
<sch:assert test="count(f:resource)=count(distinct-values(f:resource/f:type/@value))">cpb-9: A given resource can only be described once per RESTful mode.</sch:assert>
</sch:rule>
<sch:rule context="f:CapabilityStatement/f:rest/f:resource">
<sch:assert test="count(f:searchParam)=count(distinct-values(f:searchParam/f:name/@value))">cpb-12: Search parameter names must be unique in the context of a resource.</sch:assert>
</sch:rule>
</sch:pattern>
</sch:schema>

View File

@ -485,6 +485,10 @@
<developer>
<id>Cory00</id>
</developer>
<developer>
<id>srdo</id>
<name>Stig Døssing</name>
</developer>
</developers>
<licenses>

View File

@ -251,6 +251,10 @@
AuthorizationInterceptor is now able to authorize DELETE operations performed via a
transaction operation. Previously these were always denied.
</action>
<action type="add" issue="1065">
OperationDefinitions are now created for named queries in server
module. Thanks to Stig Døssing for the pull request!
</action>
</release>
<release version="3.6.0" date="2018-11-12" description="Food">
<action type="add">