Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2018-02-01 06:33:00 -05:00
commit 3f1ef0e7f6
67 changed files with 42022 additions and 5182 deletions

View File

@ -25,5 +25,5 @@ before_script:
script:
# - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
# - mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
- mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
- mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE_JDK8 clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report

View File

@ -4,4 +4,4 @@ cache:
- C:\maven\
- C:\Users\appveyor\.m2\repository
build_script:
- cmd: mvn -P MINPARALLEL,ALLMODULES install
- cmd: mvn -P MINPARALLEL,ALLMODULES,ERRORPRONE_JDK8 install

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.context;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,16 +40,14 @@ public enum FhirVersionEnum {
DSTU2_1("org.hl7.fhir.dstu2016may.hapi.ctx.FhirDstu2_1", null, true, new Version("1.4.0")),
DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()),
R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),
;
DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()),
R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),;
private final FhirVersionEnum myEquivalent;
private final boolean myIsRi;
private volatile Boolean myPresentOnClasspath;
private final String myVersionClass;
private volatile Boolean myPresentOnClasspath;
private volatile IFhirVersion myVersionImplementation;
private String myFhirVersionString;
@ -82,21 +80,6 @@ public enum FhirVersionEnum {
return ordinal() >= theVersion.ordinal();
}
/**
* Returns the {@link FhirVersionEnum} which corresponds to a specific version of
* FHIR. Partial version strings (e.g. "3.0") are acceptable.
*
* @return Returns null if no version exists matching the given string
*/
public static FhirVersionEnum forVersionString(String theVersionString) {
for (FhirVersionEnum next : values()) {
if (next.getFhirVersionString().startsWith(theVersionString)) {
return next;
}
}
return null;
}
public boolean isEquivalentTo(FhirVersionEnum theVersion) {
if (this.equals(theVersion)) {
return true;
@ -139,15 +122,50 @@ public enum FhirVersionEnum {
return myIsRi;
}
public FhirContext newContext() {
switch (this) {
case DSTU2:
return FhirContext.forDstu2();
case DSTU2_HL7ORG:
return FhirContext.forDstu2Hl7Org();
case DSTU2_1:
return FhirContext.forDstu2_1();
case DSTU3:
return FhirContext.forDstu3();
case R4:
return FhirContext.forR4();
}
throw new IllegalStateException("Unknown version: " + this); // should not happen
}
/**
* Returns the {@link FhirVersionEnum} which corresponds to a specific version of
* FHIR. Partial version strings (e.g. "3.0") are acceptable.
*
* @return Returns null if no version exists matching the given string
*/
public static FhirVersionEnum forVersionString(String theVersionString) {
for (FhirVersionEnum next : values()) {
if (next.getFhirVersionString().startsWith(theVersionString)) {
return next;
}
}
return null;
}
private interface IVersionProvider {
String provideVersion();
}
private static class Version implements IVersionProvider {
private String myVersion;
public Version(String theVersion) {
super();
myVersion = theVersion;
}
private String myVersion;
@Override
public String provideVersion() {
return myVersion;
@ -155,17 +173,14 @@ public enum FhirVersionEnum {
}
private interface IVersionProvider {
String provideVersion();
}
/**
* This class attempts to read the FHIR version from the actual model
* classes in order to supply an accurate version string even over time
*
*/
private static class Dstu3Version implements IVersionProvider {
private String myVersion;
public Dstu3Version() {
try {
Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
@ -175,8 +190,6 @@ public enum FhirVersionEnum {
}
}
private String myVersion;
@Override
public String provideVersion() {
return myVersion;
@ -186,6 +199,8 @@ public enum FhirVersionEnum {
private static class R4Version implements IVersionProvider {
private String myVersion;
public R4Version() {
try {
Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
@ -195,8 +210,6 @@ public enum FhirVersionEnum {
}
}
private String myVersion;
@Override
public String provideVersion() {
return myVersion;

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,25 +20,32 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implements IQueryParameterAnd<T> {
private List<T> myValues=new ArrayList<T>();
private List<T> myValues = new ArrayList<>();
public abstract BaseAndListParam<T> addAnd(T theValue);
public BaseAndListParam<T> addValue(T theValue) {
myValues.add(theValue);
return this;
}
public abstract BaseAndListParam<T> addAnd(T theValue);
@Override
public List<T> getValuesAsQueryTokens() {
return myValues;
}
abstract T newInstance();
@Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) throws InvalidRequestException {
@ -50,11 +57,9 @@ public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implement
}
}
abstract T newInstance();
@Override
public List<T> getValuesAsQueryTokens() {
return myValues;
public String toString() {
return myValues.toString();
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,23 +20,35 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import java.util.ArrayList;
import java.util.List;
abstract class BaseOrListParam<MT extends BaseOrListParam<?, ?>, PT extends IQueryParameterType> implements IQueryParameterOr<PT> {
private List<PT> myList=new ArrayList<PT>();
private List<PT> myList = new ArrayList<>();
@SuppressWarnings("unchecked")
public MT add(PT theParameter) {
if (theParameter != null) {
myList.add(theParameter);
}
return (MT) this;
}
public abstract MT addOr(PT theParameter);
@Override
public List<PT> getValuesAsQueryTokens() {
return myList;
}
abstract PT newInstance();
// public void addToken(T theParam) {
// Validate.notNull(theParam,"Param can not be null");
// myList.add(theParam);
// }
@Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) {
myList.clear();
@ -47,21 +59,9 @@ abstract class BaseOrListParam<MT extends BaseOrListParam<?, ?>, PT extends IQue
}
}
abstract PT newInstance();
public abstract MT addOr(PT theParameter);
@SuppressWarnings("unchecked")
public MT add(PT theParameter) {
if (theParameter != null) {
myList.add(theParameter);
}
return (MT) this;
}
@Override
public List<PT> getValuesAsQueryTokens() {
return myList;
public String toString() {
return myList.toString();
}
}

View File

@ -69,14 +69,12 @@ public class TokenOrListParam extends BaseOrListParam<TokenOrListParam, TokenPar
/**
* Add a new token to this list
*
* @param theSystem
* @param theSystem
* The system to use for the one token to pre-populate in this list
* @param theValue
* The value to use for the one token to pre-populate in this list
*/
public void add(String theSystem, String theValue) {
public TokenOrListParam add(String theSystem, String theValue) {
add(new TokenParam(theSystem, theValue));
return this;
}
public List<BaseCodingDt> getListAsCodings() {

View File

@ -191,6 +191,23 @@
<artifactId>phloc-commons</artifactId>
</dependency>
<!--
These have been added as explicit dependencies
as JDK9 no longer includes them by default
-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>

View File

@ -32,6 +32,12 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>3.3.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
@ -70,6 +76,11 @@
</dependency>
<!-- Testing -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>

View File

@ -21,39 +21,67 @@ package ca.uhn.hapi.converters.server;
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.StringTokenizer;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
/**
* <b>This is an experimental interceptor! Use with caution as
* behaviour may change or be removed in a future version of
* FHIR.</b>
* <p>
* This interceptor partially implements the proposed
* Versioned API features.
* </p>
*/
public class VersionedApiConverterInterceptor extends InterceptorAdapter {
private VersionConvertor_30_40 myVersionConvertor_30_40 = new VersionConvertor_30_40();
private final FhirContext myCtxDstu2;
private final FhirContext myCtxDstu2Hl7Org;
private VersionConvertor_30_40 myVersionConvertor_30_40;
private VersionConvertor_10_40 myVersionConvertor_10_40;
private VersionConvertor_10_30 myVersionConvertor_10_30;
public VersionedApiConverterInterceptor() {
myVersionConvertor_30_40 = new VersionConvertor_30_40();
VersionConvertorAdvisor40 advisor40 = new NullVersionConverterAdvisor40();
myVersionConvertor_10_40 = new VersionConvertor_10_40(advisor40);
VersionConvertorAdvisor30 advisor30 = new NullVersionConverterAdvisor30();
myVersionConvertor_10_30 = new VersionConvertor_10_30(advisor30);
myCtxDstu2 = FhirContext.forDstu2();
myCtxDstu2Hl7Org = FhirContext.forDstu2Hl7Org();
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
String accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT));
public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
String[] formatParams = theRequestDetails.getParameters().get(Constants.PARAM_FORMAT);
String accept = null;
if (formatParams != null && formatParams.length > 0) {
accept = formatParams[0];
}
if (isBlank(accept)) {
accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT));
}
StringTokenizer tok = new StringTokenizer(accept, ";");
String wantVersionString = null;
while (tok.hasMoreTokens()) {
String next = tok.nextToken().trim();
if (next.startsWith("fhir-version=")) {
wantVersionString = next.substring("fhir-version=".length()).trim();
if (next.startsWith("fhirVersion=")) {
wantVersionString = next.substring("fhirVersion=".length()).trim();
break;
}
}
@ -62,31 +90,48 @@ public class VersionedApiConverterInterceptor extends InterceptorAdapter {
if (isNotBlank(wantVersionString)) {
wantVersion = FhirVersionEnum.forVersionString(wantVersionString);
}
FhirVersionEnum haveVersion = theResponseObject.getStructureFhirVersionEnum();
IBaseResource responseResource = theResponseDetails.getResponseResource();
FhirVersionEnum haveVersion = responseResource.getStructureFhirVersionEnum();
IBaseResource converted = null;
try {
if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU3) {
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theResponseObject);
converted = myVersionConvertor_30_40.convertResource(toDstu3(responseResource));
} else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) {
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject);
} else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) {
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject);
converted = myVersionConvertor_30_40.convertResource(toR4(responseResource));
} else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.R4) {
converted = myVersionConvertor_10_40.convertResource(toR4(responseResource));
} else if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU2) {
converted = myVersionConvertor_10_40.convertResource(toDstu2(responseResource));
} else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.DSTU3) {
converted = myVersionConvertor_10_30.convertResource(toDstu3(responseResource));
} else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.DSTU2) {
converted = myVersionConvertor_10_30.convertResource(toDstu2(responseResource));
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
if (converted != null) {
Set<SummaryEnum> objects = Collections.emptySet();
try {
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), converted, objects, 200, "OK", false, false, theRequestDetails, null, null);
return false;
} catch (IOException e) {
throw new InternalErrorException(e);
}
theResponseDetails.setResponseResource(converted);
}
return true;
}
private org.hl7.fhir.instance.model.Resource toDstu2(IBaseResource theResponseResource) {
if (theResponseResource instanceof IResource) {
return (org.hl7.fhir.instance.model.Resource) myCtxDstu2Hl7Org.newJsonParser().parseResource(myCtxDstu2.newJsonParser().encodeResourceToString(theResponseResource));
}
return (org.hl7.fhir.instance.model.Resource) theResponseResource;
}
private Resource toDstu3(IBaseResource theResponseResource) {
return (Resource) theResponseResource;
}
private org.hl7.fhir.r4.model.Resource toR4(IBaseResource theResponseResource) {
return (org.hl7.fhir.r4.model.Resource) theResponseResource;
}
}

View File

@ -0,0 +1,56 @@
package org.hl7.fhir.convertors;
/*
* #%L
* HAPI FHIR - Converter
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.Resource;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet;
public class NullVersionConverterAdvisor40 implements VersionConvertorAdvisor40 {
@Override
public Resource convertR2(org.hl7.fhir.r4.model.Resource resource) throws FHIRException {
return null;
}
@Override
public org.hl7.fhir.dstu3.model.Resource convertR3(org.hl7.fhir.r4.model.Resource resource) throws FHIRException {
return null;
}
@Override
public CodeSystem getCodeSystem(ValueSet theSrc) {
return null;
}
@Override
public void handleCodeSystem(CodeSystem theTgtcs, ValueSet theSource) {
//nothing
}
@Override
public boolean ignoreEntry(BundleEntryComponent theSrc) {
return false;
}
}

View File

@ -1,276 +0,0 @@
package ca.uhn.hapi.converters.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SearchStyleEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
public class SearchDstu3Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static TokenAndListParam ourIdentifiers;
private static String ourLastMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDstu3Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastMethod = null;
ourIdentifiers = null;
}
@Test
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("search", ourLastMethod);
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testSearchWithInvalidChain() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
assertEquals(
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
oo.getIssueFirstRep().getDiagnostics());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testPagingPreservesEncodingJson() throws Exception {
HttpGet httpGet;
String linkNext;
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
}
@Test
public void testPagingPreservesEncodingApplicationJsonFhir() throws Exception {
HttpGet httpGet;
String linkNext;
Bundle bundle;
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
}
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
CloseableHttpResponse status = ourClient.execute(httpGet);
Bundle bundle;
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
assertEquals(10, bundle.getEntry().size());
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
return bundle;
}
@Test
public void testSearchWithPostAndInvalidParameters() {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setLogRequestSummary(true);
interceptor.setLogRequestBody(true);
interceptor.setLogRequestHeaders(false);
interceptor.setLogResponseBody(false);
interceptor.setLogResponseHeaders(false);
interceptor.setLogResponseSummary(false);
client.registerInterceptor(interceptor);
try {
client
.search()
.forResource(Patient.class)
.where(new StringClientParam("foo").matches().value("bar"))
.prettyPrint()
.usingStyle(SearchStyleEnum.POST)
.returnBundle(Bundle.class)
.encodedJson()
.execute();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]"));
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@SuppressWarnings("rawtypes")
@Search()
public List search(
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
ourLastMethod = "search";
ourIdentifiers = theIdentifiers;
ArrayList<Patient> retVal = new ArrayList<Patient>();
for (int i = 0; i < 200; i++) {
Patient patient = new Patient();
patient.addName(new HumanName().setFamily("FAMILY"));
patient.getIdElement().setValue("Patient/" + i);
retVal.add(patient);
}
return retVal;
}
}
}

View File

@ -0,0 +1,131 @@
package ca.uhn.hapi.converters.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
public class VersionedApiConverterInterceptorR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(VersionedApiConverterInterceptorR4Test.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static int ourPort;
private static Server ourServer;
@Test
public void testSearchNormal() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertThat(responseContent, containsString("\"family\": \"FAMILY\""));
}
}
@Test
public void testSearchConvertToR2() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
httpGet.addHeader("Accept", "application/fhir+json; fhirVersion=1.0");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertThat(responseContent, containsString("\"family\": ["));
}
}
@Test
public void testSearchConvertToR2ByFormatParam() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=" + UrlUtil.escapeUrlParam("application/fhir+json; fhirVersion=1.0"));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertThat(responseContent, containsString("\"family\": ["));
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
servlet.setDefaultPrettyPrint(true);
servlet.registerInterceptor(new VersionedApiConverterInterceptor());
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@SuppressWarnings("rawtypes")
@Search()
public List search() {
ArrayList<Patient> retVal = new ArrayList<>();
Patient patient = new Patient();
patient.getIdElement().setValue("Patient/A");
patient.addName(new HumanName().setFamily("FAMILY"));
retVal.add(patient);
return retVal;
}
}
}

View File

@ -0,0 +1,48 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse.jetty.websocket" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse" additivity="false" level="error">
</logger>
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL logging -->
<logger name="org.hibernate.SQL" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL Value logging -->
<logger name="org.hibernate.type" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -91,7 +91,7 @@ public class JaxRsResponse extends RestfulResponse<JaxRsRequest> {
StringWriter writer = new StringWriter();
if (outcome != null) {
FhirContext fhirContext = getRequestDetails().getServer().getFhirContext();
IParser parser = RestfulServerUtils.getNewParser(fhirContext, getRequestDetails());
IParser parser = RestfulServerUtils.getNewParser(fhirContext, fhirContext.getVersion().getVersion(), getRequestDetails());
outcome.execute(parser, writer);
}
return sendWriterResponse(operationStatus, getParserType(), null, writer);

View File

@ -114,7 +114,7 @@ public class JaxRsResponseDstu3Test {
boolean allowPrefer = true;
String resourceName = "Patient";
MethodOutcome methodOutcome = new MethodOutcome(theId);
response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
boolean addContentLocationHeader = true;
boolean respondGzip = true;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request);
@ -126,7 +126,7 @@ public class JaxRsResponseDstu3Test {
@Test
public void testNoOutcomeXml() throws IOException {
response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
boolean addContentLocationHeader = true;
boolean respondGzip = true;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), null, theSummaryMode, 204, addContentLocationHeader, respondGzip, this.request);

View File

@ -88,7 +88,7 @@ public class JaxRsResponseTest {
@Test
public void testReturnResponseAsXml() throws IOException {
response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML });
response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML });
boolean addContentLocationHeader = true;
boolean respondGzip = true;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request);
@ -100,7 +100,7 @@ public class JaxRsResponseTest {
@Test
public void testNoOutcomeXml() throws IOException {
response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML });
response.getRequestDetails().addParameter(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML });
boolean addContentLocationHeader = true;
boolean respondGzip = true;
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), createPatient(), theSummaryMode, 200, addContentLocationHeader, respondGzip, this.request);

View File

@ -550,6 +550,27 @@
</configuration>
</execution>
</executions>
<dependencies>
<!--
These have been added as explicit dependencies
as JDK9 no longer includes them by default
-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb_api_version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>${jaxb_core_version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb_core_version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>

View File

@ -18,7 +18,7 @@ import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -66,7 +66,7 @@ public class BaseDstu3Config extends BaseConfig {
@Lazy
public IValidatorModule instanceValidatorDstu3() {
FhirInstanceValidator val = new FhirInstanceValidator();
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
val.setValidationSupport(validationSupportChainDstu3());
return val;
}

View File

@ -532,7 +532,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
}
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
}

View File

@ -260,7 +260,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
}

View File

@ -266,7 +266,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
}
@ -290,7 +290,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch());
}
Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url);
Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {0}", url);
try {
IBaseResource resource = ((BaseResourceReturningMethodBinding) method).doInvokeServer(theRequestDetails.getServer(), requestDetails);
if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) {

View File

@ -29,13 +29,13 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class ServletSubRequestDetails extends ServletRequestDetails {
private Map<String, ArrayList<String>> myHeaders = new HashMap<String, ArrayList<String>>();
private Map<String, ArrayList<String>> myHeaders = new HashMap<>();
public void addHeader(String theName, String theValue) {
String lowerCase = theName.toLowerCase();
ArrayList<String> list = myHeaders.get(lowerCase);
if (list == null) {
list = new ArrayList<String>();
list = new ArrayList<>();
myHeaders.put(lowerCase, list);
}
list.add(theValue);

View File

@ -158,7 +158,7 @@ public abstract class BaseJpaTest {
}
protected List<IIdType> toUnqualifiedVersionlessIds(IBundleProvider theFound) {
List<IIdType> retVal = new ArrayList<IIdType>();
List<IIdType> retVal = new ArrayList<>();
Integer size = theFound.size();
StopWatch sw = new StopWatch();
while (size == null) {
@ -171,6 +171,7 @@ public abstract class BaseJpaTest {
} catch (InterruptedException theE) {
//ignore
}
size = theFound.size();
}
ourLog.info("Found {} results", size);

View File

@ -46,6 +46,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Before
public void before() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
myDaoConfig.setSchedulingDisabled(new DaoConfig().isSchedulingDisabled());
}
private void createUniqueBirthdateAndGenderSps() {
@ -411,6 +412,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
@Test
public void testDuplicateUniqueValuesAreReIndexed() {
myDaoConfig.setSchedulingDisabled(true);
Patient pt1 = new Patient();
pt1.setActive(true);
@ -449,7 +451,9 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myResourceIndexedCompositeStringUniqueDao.deleteAll();
mySystemDao.markAllResourcesForReindexing();
assertEquals(1, mySearchParamRegsitry.getActiveUniqueSearchParams("Observation").size());
assertEquals(7, mySystemDao.markAllResourcesForReindexing());
mySystemDao.performReindexingPass(1000);
mySystemDao.performReindexingPass(1000);

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -12,6 +11,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@ -24,7 +24,6 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
import org.junit.*;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
@ -2448,7 +2447,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
/*
* Make sure we are able to handle placeholder IDs in match URLs, e.g.
*
*
* "request": {
* "method": "PUT",
* "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"
@ -2478,7 +2477,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
/*
* Make sure we are able to handle placeholder IDs in match URLs, e.g.
*
*
* "request": {
* "method": "PUT",
* "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"
@ -2542,7 +2541,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testTransactionWithReferenceResource() {
Bundle request = new Bundle();
@ -2570,7 +2568,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithReferenceToCreateIfNoneExist() {
Bundle bundle = new Bundle();
@ -2594,9 +2591,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
/*
* Again!
*/
/*
* Again!
*/
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
@ -2624,6 +2621,33 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
assertNotEquals(medOrderId1, medOrderId2);
}
@Test
public void testTransactionWithReferenceUuid() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference(p.getId());
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
//
//
// /**
@ -2726,34 +2750,6 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
//
// }
@Test
public void testTransactionWithReferenceUuid() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference(p.getId());
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithRelativeOidIds() throws Exception {
Bundle res = new Bundle();
@ -2918,6 +2914,46 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
}
/**
* See #811
*/
@Test
public void testUpdatePreviouslyDeletedResourceInBatch() {
AllergyIntolerance ai = new AllergyIntolerance();
ai.setId("AIA1914009");
ai.setClinicalStatus(AllergyIntolerance.AllergyIntoleranceClinicalStatus.ACTIVE);
IIdType id = myAllergyIntoleranceDao.update(ai).getId();
assertEquals("1", id.getVersionIdPart());
id = myAllergyIntoleranceDao.delete(ai.getIdElement().toUnqualifiedVersionless()).getId();
assertEquals("2", id.getVersionIdPart());
try {
myAllergyIntoleranceDao.read(ai.getIdElement().toUnqualifiedVersionless());
fail();
} catch (ResourceGoneException e) {
// good
}
Bundle batch = new Bundle();
batch.setType(BundleType.BATCH);
ai = new AllergyIntolerance();
ai.setId("AIA1914009");
ai.setClinicalStatus(AllergyIntolerance.AllergyIntoleranceClinicalStatus.ACTIVE);
batch
.addEntry()
.setFullUrl("AllergyIntolerance/AIA1914009")
.setResource(ai)
.getRequest()
.setUrl("AllergyIntolerance/AIA1914009")
.setMethod(HTTPVerb.PUT);
mySystemDao.transaction(mySrd, batch);
id = myAllergyIntoleranceDao.read(ai.getIdElement().toUnqualifiedVersionless()).getIdElement();
assertEquals("3", id.getVersionIdPart());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -1,35 +1,41 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.rp.r4.*;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.TestUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.*;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Patient;
import org.junit.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.rp.r4.*;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.TestUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
@ -43,7 +49,6 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor;
@SuppressWarnings("deprecation")
@After
public void after() {
@ -57,7 +62,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor();
ourClient.registerInterceptor(mySimpleHeaderInterceptor);
}
@Before
public void beforeStartServer() throws Exception {
if (myRestServer == null) {
@ -73,8 +78,14 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(myOrganizationDao);
MedicationResourceProvider medicationRp = new MedicationResourceProvider();
medicationRp.setDao(myMedicationDao);
MedicationRequestResourceProvider medicationRequestRp = new MedicationRequestResourceProvider();
medicationRequestRp.setDao(myMedicationRequestDao);
RestfulServer restServer = new RestfulServer(ourCtx);
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp, medicationRequestRp, medicationRp);
restServer.setPlainProviders(mySystemProvider);
@ -106,11 +117,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
ourClient.setLogRequestAndResponse(true);
myRestServer = restServer;
}
myRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
myRestServer.setPagingProvider(myPagingProvider);
}
private List<String> create20Patients() {
List<String> ids = new ArrayList<String>();
@ -120,7 +130,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
patient.setId("" + letter);
patient.setGender(AdministrativeGender.MALE);
patient.addIdentifier().setSystem("urn:foo").setValue("A");
patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i+1));
patient.addName().setFamily("abcdefghijklmnopqrstuvwxyz".substring(i, i + 1));
String id = myPatientDao.update(patient).getId().toUnqualifiedVersionless().getValue();
ids.add(id);
}
@ -130,7 +140,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
@Test
public void testBatchWithGetHardLimitLargeSynchronous() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.BATCH);
input
@ -138,12 +148,12 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.getRequest()
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&_sort=_id");
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(1, output.getEntry().size());
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
assertEquals(5, respBundle.getEntry().size());
@ -151,11 +161,11 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
List<String> actualIds = toIds(respBundle);
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
}
@Test
public void testBatchWithGetNormalSearch() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.BATCH);
input
@ -163,16 +173,16 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.getRequest()
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&_sort=name");
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(1, output.getEntry().size());
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
assertEquals(5, respBundle.getEntry().size());
List<String> actualIds = toIds(respBundle);
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
String nextPageLink = respBundle.getLink("next").getUrl();
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
respBundle = output;
@ -188,7 +198,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
public void testBatchWithManyGets() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.BATCH);
for (int i = 0; i < 30; i++) {
@ -198,10 +208,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
}
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(30, output.getEntry().size());
for (int i = 0; i < 30; i++) {
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();
@ -212,10 +222,72 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
}
}
/**
* See #822
*/
@Test
public void testSearchByBatch() {
Patient p = new Patient();
p.setId("P3000254749");
p.setActive(true);
myPatientDao.update(p);
Medication med = new Medication();
med.setId("MED19795");
med.getCode().addCoding().setCode("00093-0058-05").setSystem("http://hl7.org/fhir/sid/ndc");
myMedicationDao.update(med);
med = new Medication();
med.setId("MED20344");
med.getCode().addCoding().setCode("50580-0449-23").setSystem("http://hl7.org/fhir/sid/ndc");
myMedicationDao.update(med);
MedicationRequest medRequest = new MedicationRequest();
medRequest.setId("MR142528");
medRequest.setMedication(new Reference("Medication/MED19795"));
medRequest.setSubject(new Reference("Patient/P3000254749"));
medRequest.setIntent(MedicationRequest.MedicationRequestIntent.ORDER);
myMedicationRequestDao.update(medRequest);
medRequest = new MedicationRequest();
medRequest.setId("MR635079");
medRequest.setMedication(new Reference("Medication/MED20344"));
medRequest.setSubject(new Reference("Patient/P3000254749"));
medRequest.setIntent(MedicationRequest.MedicationRequestIntent.ORDER);
myMedicationRequestDao.update(medRequest);
SearchParameterMap map = new SearchParameterMap();
map.add(MedicationRequest.SP_INTENT, new TokenOrListParam().add(null, "plan").add(null, "order"));
map.add(MedicationRequest.SP_MEDICATION, new ReferenceParam().setChain("code").setValue("50580-0449-23"));
Bundle b = ourClient
.search()
.forResource("MedicationRequest")
.where(MedicationRequest.INTENT.exactly().codes("plan", "order"))
.and(MedicationRequest.MEDICATION.hasChainedProperty(Medication.CODE.exactly().code("50580-0449-23")))
.returnBundle(Bundle.class)
.execute();
assertEquals(1, b.getEntry().size());
assertEquals("MedicationRequest/MR635079", b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
b = new Bundle();
b.setType(BundleType.BATCH);
b.addEntry()
.setFullUrl(IdType.newRandomUuid().getValueAsString())
.getRequest()
.setMethod(HTTPVerb.GET)
.setUrl("MedicationRequest?intent=plan,order&medication.code=50580-0449-23&patient=P3000254749");
Bundle resp = ourClient.transaction().withBundle(b).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
b = (Bundle) resp.getEntry().get(0).getResource();
assertEquals(1, b.getEntry().size());
assertEquals("MedicationRequest/MR635079", b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
@Test
public void testTransactionWithGetHardLimitLargeSynchronous() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.TRANSACTION);
input
@ -223,12 +295,12 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.getRequest()
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&_sort=_id");
myDaoConfig.setMaximumSearchResultCountInTransaction(100);
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(1, output.getEntry().size());
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
assertEquals(5, respBundle.getEntry().size());
@ -236,11 +308,11 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
List<String> actualIds = toIds(respBundle);
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
}
@Test
public void testTransactionWithGetNormalSearch() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.TRANSACTION);
input
@ -248,16 +320,16 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.getRequest()
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&_sort=name");
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(1, output.getEntry().size());
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
assertEquals(5, respBundle.getEntry().size());
List<String> actualIds = toIds(respBundle);
assertThat(actualIds, contains(ids.subList(0, 5).toArray(new String[0])));
String nextPageLink = respBundle.getLink("next").getUrl();
output = ourClient.loadPage().byUrl(nextPageLink).andReturnBundle(Bundle.class).execute();
respBundle = output;
@ -273,7 +345,7 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
public void testTransactionWithManyGets() {
List<String> ids = create20Patients();
Bundle input = new Bundle();
input.setType(BundleType.TRANSACTION);
for (int i = 0; i < 30; i++) {
@ -283,10 +355,10 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
.setMethod(HTTPVerb.GET)
.setUrl("Patient?_count=5&identifier=urn:foo|A,AAAAA" + i);
}
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(30, output.getEntry().size());
for (int i = 0; i < 30; i++) {
Bundle respBundle = (Bundle) output.getEntry().get(i).getResource();

View File

@ -155,6 +155,11 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-converter</artifactId>
<version>3.3.0-SNAPSHOT</version>
</dependency>
</dependencies>

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.*;
import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
@ -176,6 +177,11 @@ public class TestRestfulServer extends RestfulServer {
CorsInterceptor corsInterceptor = new CorsInterceptor();
registerInterceptor(corsInterceptor);
/*
* Enable version conversion
*/
registerInterceptor(new VersionedApiConverterInterceptor());
/*
* We want to format the response using nice HTML if it's a browser, since this
* makes things a little easier for testers.

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -30,9 +31,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -63,6 +64,11 @@ public abstract class RequestDetails {
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
private Map<Object, Object> myUserData;
public void addParameter(String theName, String[] theValues) {
getParameters();
myParameters.put(theName, theValues);
}
protected abstract byte[] getByteStreamRequestContents();
/**
@ -170,37 +176,14 @@ public abstract class RequestDetails {
public Map<String, String[]> getParameters() {
if (myParameters == null) {
return Collections.emptyMap();
myParameters = new HashMap<>();
}
return myParameters;
return Collections.unmodifiableMap(myParameters);
}
public void setParameters(Map<String, String[]> theParams) {
myParameters = theParams;
for (String next : theParams.keySet()) {
for (int i = 0; i < next.length(); i++) {
char nextChar = next.charAt(i);
if (nextChar == ':' || nextChar == '.') {
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = new HashMap<>();
}
String unqualified = next.substring(0, i);
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
if (list == null) {
list = new ArrayList<>(4);
myUnqualifiedToQualifiedNames.put(unqualified, list);
}
list.add(next);
break;
}
}
}
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = Collections.emptyMap();
}
myUnqualifiedToQualifiedNames = null;
}
/**
@ -296,6 +279,31 @@ public abstract class RequestDetails {
}
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
if (myUnqualifiedToQualifiedNames == null) {
for (String next : myParameters.keySet()) {
for (int i = 0; i < next.length(); i++) {
char nextChar = next.charAt(i);
if (nextChar == ':' || nextChar == '.') {
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = new HashMap<>();
}
String unqualified = next.substring(0, i);
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
if (list == null) {
list = new ArrayList<>(4);
myUnqualifiedToQualifiedNames.put(unqualified, list);
}
list.add(next);
break;
}
}
}
}
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = Collections.emptyMap();
}
return myUnqualifiedToQualifiedNames;
}
@ -359,6 +367,12 @@ public abstract class RequestDetails {
return myRequestContents;
}
public void removeParameter(String theName) {
Validate.notNull(theName, "theName must not be null");
getParameters();
myParameters.remove(theName);
}
/**
* This method may be used to modify the contents of the incoming
* request by hardcoding a value which will be used instead of the

View File

@ -938,8 +938,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/*
* If we're handling an exception, no summary mode should be applied
*/
requestDetails.getParameters().remove(Constants.PARAM_SUMMARY);
requestDetails.getParameters().remove(Constants.PARAM_ELEMENTS);
requestDetails.removeParameter(Constants.PARAM_SUMMARY);
requestDetails.removeParameter(Constants.PARAM_ELEMENTS);
/*
* If nobody handles it, default behaviour is to stream back the OperationOutcome to the client.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -58,6 +58,7 @@ public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text", "*.(mandatory)"));
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<FhirVersionEnum, FhirContext>());
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
// Pretty print
@ -266,7 +267,7 @@ public class RestfulServerUtils {
* Some browsers (e.g. FF) request "application/xml" in their Accept header,
* and we generally want to treat this as a preference for FHIR XML even if
* it's not the FHIR version of the CT, which should be "application/xml+fhir".
*
*
* When we're serving up Binary resources though, we are a bit more strict,
* since Binary is supposed to use native content types unless the client has
* explicitly requested FHIR.
@ -433,7 +434,9 @@ public class RestfulServerUtils {
if (theResourceId.hasIdPart() && isNotBlank(theServerBase)) {
String resName = theResourceId.getResourceType();
if (theResource != null && isBlank(resName)) {
resName = theServer.getFhirContext().getResourceDefinition(theResource).getName();
FhirContext context = theServer.getFhirContext();
context = getContextForVersion(context, theResource.getStructureFhirVersionEnum());
resName = context.getResourceDefinition(theResource).getName();
}
if (isNotBlank(resName)) {
retVal = theResourceId.withServerBase(theServerBase, resName);
@ -455,18 +458,19 @@ public class RestfulServerUtils {
return new ResponseEncoding(theFhirContext, encoding, theContentType);
}
public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) {
public static IParser getNewParser(FhirContext theContext, FhirVersionEnum theForVersion, RequestDetails theRequestDetails) {
FhirContext context = getContextForVersion(theContext, theForVersion);
// Determine response encoding
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails).getEncoding();
IParser parser;
switch (responseEncoding) {
case JSON:
parser = theContext.newJsonParser();
parser = context.newJsonParser();
break;
case XML:
default:
parser = theContext.newXmlParser();
parser = context.newXmlParser();
break;
}
@ -475,6 +479,18 @@ public class RestfulServerUtils {
return parser;
}
private static FhirContext getContextForVersion(FhirContext theContext, FhirVersionEnum theForVersion) {
FhirContext context = theContext;
if (context.getVersion().getVersion() != theForVersion) {
context = myFhirContextMap.get(theForVersion);
if (context == null) {
context = theForVersion.newContext();
myFhirContextMap.put(theForVersion, context);
}
}
return context;
}
public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
Set<String> retVal = new HashSet<String>();
@ -689,7 +705,8 @@ public class RestfulServerUtils {
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
} else {
IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails);
FhirVersionEnum forVersion = theResource.getStructureFhirVersionEnum();
IParser parser = getNewParser(theServer.getFhirContext(), forVersion, theRequestDetails);
parser.encodeResourceToWriter(theResource, writer);
}
//FIXME resource leak

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
@ -322,10 +323,10 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
force = true;
} else if (Constants.FORMATS_HTML_XML.equals(formatParam)) {
force = true;
theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_XML);
theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_XML);
} else if (Constants.FORMATS_HTML_JSON.equals(formatParam)) {
force = true;
theRequestDetails.getParameters().put(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON);
theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON);
} else {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
@ -392,7 +393,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
}
}
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource, ServletRequest theServletRequest, int theStatusCode) {
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource theResource, ServletRequest theServletRequest, int theStatusCode) {
if (theRequestDetails.getServer() instanceof RestfulServer) {
RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
@ -402,7 +403,8 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
IParser p;
Map<String, String[]> parameters = theRequestDetails.getParameters();
if (parameters.containsKey(Constants.PARAM_FORMAT)) {
p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails);
FhirVersionEnum forVersion = theResource.getStructureFhirVersionEnum();
p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), forVersion, theRequestDetails);
} else {
EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
@ -423,7 +425,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
}
EncodingEnum encoding = p.getEncoding();
String encoded = p.encodeResourceToString(resource);
String encoded = p.encodeResourceToString(theResource);
try {
@ -615,7 +617,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
b.append("\n");
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript resource not found')";
String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')";
jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest());
b.append("<script type=\"text/javascript\">");
b.append(jsStr);

View File

@ -96,7 +96,7 @@ public abstract class BaseQueryParameter implements IParameter {
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
List<QualifiedParamList> paramList = new ArrayList<QualifiedParamList>();
List<QualifiedParamList> paramList = new ArrayList<>();
String name = getName();
parseParams(theRequest, paramList, name, null);

View File

@ -1,354 +1,335 @@
package org.hl7.fhir.dstu3.context;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.dstu3.formats.IParser;
import org.hl7.fhir.dstu3.formats.ParserType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.ExpansionProfile;
import org.hl7.fhir.dstu3.model.MetadataResource;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.dstu3.utils.INarrativeGenerator;
import org.hl7.fhir.dstu3.utils.IResourceValidator;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
/**
* 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 {
/**
* Get the versions of the definitions loaded in context
* @return
*/
public String getVersion();
// -- 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 generator that can generate narrative for the instance
*
* @return a prepared generator
*/
public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath);
/**
* 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;
/**
* 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);
// -- profile services ---------------------------------------------------------
public List<String> getResourceNames();
public List<String> getTypeNames();
public List<StructureDefinition> allStructures();
public List<MetadataResource> allConformanceResources();
// -- Terminology services ------------------------------------------------------
public ExpansionProfile getExpansionProfile();
public void setExpansionProfile(ExpansionProfile expProfile);
// 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
*/
public List<ConceptMap> findMapsForSource(String url);
/**
* ValueSet Expansion - see $expand
*
* @param source
* @return
*/
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
/**
* 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
*/
public ValueSetExpansionComponent expandVS(ConceptSetComponent inc, boolean heiarchical) throws TerminologyServiceException;
public class ValidationResult {
private ConceptDefinitionComponent definition;
private IssueSeverity severity;
private String message;
private TerminologyServiceErrorClass errorClass;
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 definition != null;
}
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;
}
}
/**
* Validation of a code - consult the terminology 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 system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display);
/**
* Validation of a code - consult the terminology service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display, ValueSet vs);
public ValidationResult validateCode(Coding code, ValueSet vs);
public ValidationResult validateCode(CodeableConcept code, ValueSet vs);
/**
* Validation of a code - consult the terminology service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set fragment (for supported systems with no value set definition)
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display, ConceptSetComponent vsi);
/**
* returns the recommended tla for the type
*
* @param name
* @return
*/
public String getAbbreviation(String name);
// return a set of types that have tails
public Set<String> typeTails();
public String oid2Uri(String code);
public boolean hasCache();
public interface ILoggingService {
public enum LogCategory {
PROGRESS, TX, INIT, CONTEXT, 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 boolean isNoTerminologyServer();
}
package org.hl7.fhir.dstu3.context;
import org.hl7.fhir.dstu3.formats.IParser;
import org.hl7.fhir.dstu3.formats.ParserType;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.dstu3.utils.INarrativeGenerator;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.util.List;
import java.util.Set;
/**
* 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 {
/**
* Get the versions of the definitions loaded in context
* @return
*/
public String getVersion();
// -- 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 generator that can generate narrative for the instance
*
* @return a prepared generator
*/
public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath);
// -- 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
*
* @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;
/**
* 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);
// -- profile services ---------------------------------------------------------
public List<String> getResourceNames();
public List<String> getTypeNames();
public List<StructureDefinition> allStructures();
public List<MetadataResource> allConformanceResources();
// -- Terminology services ------------------------------------------------------
public ExpansionProfile getExpansionProfile();
public void setExpansionProfile(ExpansionProfile expProfile);
// 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
*/
public List<ConceptMap> findMapsForSource(String url);
/**
* ValueSet Expansion - see $expand
*
* @param source
* @return
*/
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
/**
* 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
*/
public ValueSetExpansionComponent expandVS(ConceptSetComponent inc, boolean heiarchical) throws TerminologyServiceException;
public class ValidationResult {
private ConceptDefinitionComponent definition;
private IssueSeverity severity;
private String message;
private TerminologyServiceErrorClass errorClass;
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 definition != null;
}
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;
}
}
/**
* Validation of a code - consult the terminology 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 system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display);
/**
* Validation of a code - consult the terminology service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display, ValueSet vs);
public ValidationResult validateCode(Coding code, ValueSet vs);
public ValidationResult validateCode(CodeableConcept code, ValueSet vs);
/**
* Validation of a code - consult the terminology service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set fragment (for supported systems with no value set definition)
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @return
*/
public ValidationResult validateCode(String system, String code, String display, ConceptSetComponent vsi);
/**
* returns the recommended tla for the type
*
* @param name
* @return
*/
public String getAbbreviation(String name);
// return a set of types that have tails
public Set<String> typeTails();
public String oid2Uri(String code);
public boolean hasCache();
public interface ILoggingService {
public enum LogCategory {
PROGRESS, TX, INIT, CONTEXT, 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 boolean isNoTerminologyServer();
}

View File

@ -18,7 +18,6 @@ import org.hl7.fhir.dstu3.terminologies.ValueSetExpander;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderFactory;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.dstu3.utils.INarrativeGenerator;
import org.hl7.fhir.dstu3.utils.IResourceValidator;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -124,10 +123,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
throw new UnsupportedOperationException();
}
@Override
public IResourceValidator newValidator() {
throw new UnsupportedOperationException();
}
@Override
public IParser newXmlParser() {
@ -204,8 +199,8 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
}
}
}
boolean caseSensitive = true;
if (isNotBlank(theSystem)) {
CodeSystem system = fetchCodeSystem(theSystem);

View File

@ -1,174 +0,0 @@
package org.hl7.fhir.dstu3.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.hl7.fhir.dstu3.elementmodel.Element;
import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.utils.IResourceValidator.ReferenceValidationPolicy;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import com.google.gson.JsonObject;
/**
* Interface to the instance validator. This takes a resource, in one of many forms, and
* checks whether it is valid
*
* @author Grahame Grieve
*
*/
public interface IResourceValidator {
public enum ReferenceValidationPolicy {
IGNORE, CHECK_TYPE_IF_EXISTS, CHECK_EXISTS, CHECK_EXISTS_AND_TYPE, CHECK_VALID;
public boolean checkExists() {
return this == CHECK_EXISTS_AND_TYPE || this == CHECK_EXISTS || this == CHECK_VALID;
}
public boolean checkType() {
return this == CHECK_TYPE_IF_EXISTS || this == CHECK_EXISTS_AND_TYPE || this == CHECK_VALID;
}
public boolean checkValid() {
return this == CHECK_VALID;
}
}
public interface IValidatorResourceFetcher {
Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, IOException, FHIRException;
ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url);
boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException;
}
public enum BestPracticeWarningLevel {
Ignore,
Hint,
Warning,
Error
}
public enum CheckDisplayOption {
Ignore,
Check,
CheckCaseAndSpace,
CheckCase,
CheckSpace
}
enum IdStatus {
OPTIONAL, REQUIRED, PROHIBITED
}
/**
* how much to check displays for coded elements
* @return
*/
CheckDisplayOption getCheckDisplay();
void setCheckDisplay(CheckDisplayOption checkDisplay);
/**
* whether the resource must have an id or not (depends on context)
*
* @return
*/
IdStatus getResourceIdRule();
void setResourceIdRule(IdStatus resourceIdRule);
/**
* whether the validator should enforce best practice guidelines
* as defined by various HL7 committees
*
*/
BestPracticeWarningLevel getBasePracticeWarningLevel();
IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value);
IValidatorResourceFetcher getFetcher();
IResourceValidator setFetcher(IValidatorResourceFetcher value);
boolean isNoBindingMsgSuppressed();
IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed);
public boolean isNoInvariantChecks();
public IResourceValidator setNoInvariantChecks(boolean value) ;
public boolean isNoTerminologyChecks();
public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks);
/**
* Whether being unable to resolve a profile in found in Resource.meta.profile or ElementDefinition.type.profile or targetProfile is an error or just a warning
* @return
*/
public boolean isErrorForUnknownProfiles();
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles);
/**
* Validate suite
*
* you can validate one of the following representations of resources:
*
* stream - provide a format - this is the preferred choice
*
* Use one of these two if the content is known to be valid XML/JSON, and already parsed
* - a DOM element or Document
* - a Json Object
*
* In order to use these, the content must already be parsed - e.g. it must syntactically valid
* - a native resource
* - a elementmodel resource
*
* in addition, you can pass one or more profiles ti validate beyond the base standard - as structure definitions or canonical URLs
* @throws IOException
*/
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.elementmodel.Element element) throws FHIRException, IOException;
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.elementmodel.Element element, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.elementmodel.Element element, String profile) throws FHIRException, IOException;
@Deprecated
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.elementmodel.Element element, StructureDefinition profile) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format, StructureDefinition profile) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.model.Resource resource) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.model.Resource resource, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.model.Resource resource, String profile) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.dstu3.model.Resource resource, StructureDefinition profile) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Element element, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Element element, StructureDefinition profile) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Document document) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Document document, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Document document, String profile) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, org.w3c.dom.Document document, StructureDefinition profile) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, JsonObject object) throws FHIRException, IOException;
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, JsonObject object, ValidationProfileSet profiles) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, JsonObject object, String profile) throws FHIRException, IOException;
@Deprecated
org.hl7.fhir.dstu3.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, JsonObject object, StructureDefinition profile) throws FHIRException, IOException;
}

View File

@ -165,7 +165,7 @@ public class SearchDefaultMethodDstu3Test {
assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1));
assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
assertThat(ourLastParam2.getValuesAsQueryTokens(), hasSize(2));
assertThat(ourLastParam2.toString(), ourLastParam2.getValuesAsQueryTokens(), hasSize(2));
assertThat(ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1));
assertEquals("val2", ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("val2e", ourLastParam2.getValuesAsQueryTokens().get(1).getValuesAsQueryTokens().get(0).getValue());

View File

@ -182,11 +182,21 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<reporting>

View File

@ -15,6 +15,12 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException;
import ca.uhn.fhir.util.TestUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import java.util.Set;
public class ExceptionPropertiesTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionPropertiesTest.class);
@ -37,13 +43,15 @@ public class ExceptionPropertiesTest {
@SuppressWarnings("deprecation")
@Test
public void testExceptionsAreGood() throws Exception {
ImmutableSet<ClassInfo> classes = ClassPath.from(Thread.currentThread().getContextClassLoader()).getTopLevelClasses(BaseServerResponseException.class.getPackage().getName());
assertTrue(classes.size() > 5);
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(BaseServerResponseException.class));
Set<BeanDefinition> classes = scanner.findCandidateComponents(BaseServerResponseException.class.getPackage().getName());
assertTrue(classes.toString(), classes.size() > 5);
for (ClassInfo classInfo : classes) {
ourLog.info("Scanning {}", classInfo.getName());
for (BeanDefinition classInfo : classes) {
ourLog.info("Scanning {}", classInfo.getBeanClassName());
Class<?> next = Class.forName(classInfo.getName());
Class<?> next = Class.forName(classInfo.getBeanClassName());
assertNotNull(next);
if (next == getClass()) {
@ -69,7 +77,7 @@ public class ExceptionPropertiesTest {
try {
next.getConstructor(String.class, IBaseOperationOutcome.class);
} catch (NoSuchMethodException e) {
fail(classInfo.getName() + " has no constructor with params: (String, IBaseOperationOutcome)");
fail(classInfo.getBeanClassName() + " has no constructor with params: (String, IBaseOperationOutcome)");
}
}

View File

@ -25,6 +25,11 @@
<artifactId>hapi-fhir-utilities</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-converter</artifactId>
<version>${project.version}</version>
</dependency>
<!--
Optional dependencies from RI codebase
@ -105,12 +110,6 @@
<artifactId>woodstox-core-asl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-converter</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server</artifactId>

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
@ -121,7 +122,15 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
return provideStructureDefinitionMap(theContext).get(url);
Map<String, StructureDefinition> map = provideStructureDefinitionMap(theContext);
StructureDefinition retVal = map.get(url);
if (retVal == null && url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
String tryUrl = URL_PREFIX_STRUCTURE_DEFINITION + StringUtils.capitalize(url.substring(URL_PREFIX_STRUCTURE_DEFINITION.length()));
retVal = map.get(tryUrl);
}
return retVal;
}
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {

View File

@ -1,284 +1,609 @@
package org.hl7.fhir.dstu3.hapi.validation;
import java.io.StringReader;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.dstu3.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.dstu3.validation.InstanceValidator;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.google.gson.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.formats.IParser;
import org.hl7.fhir.r4.formats.ParserType;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.r4.utils.INarrativeGenerator;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.r4.validation.InstanceValidator;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.util.*;
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
/**
* Constructor
*
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
}
/**
* Constructor
* <p>
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
}
/**
* Constructor which uses the given validation support
*
* @param theValidationSupport
* The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
/**
* Constructor which uses the given validation support
*
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
private String determineResourceName(Document theDocument) {
Element root = null;
private String determineResourceName(Document theDocument) {
Element root = null;
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <p>
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <p>
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
}
/**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
/**
* Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
* this level.
* <p>
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @param theBestPracticeWarningLevel The level, must not be <code>null</code>
*/
public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
Validate.notNull(theBestPracticeWarningLevel);
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
/**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
/**
* Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) {
noTerminologyChecks = theNoTerminologyChecks;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
/**
* Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
* this level.
* <p>
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @param theBestPracticeWarningLevel
* The level, must not be <code>null</code>
*/
public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
Validate.notNull(theBestPracticeWarningLevel);
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) {
noTerminologyChecks = theNoTerminologyChecks;
}
/**
* Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
WorkerContextWrapper wrappedWorkerContext = new WorkerContextWrapper(workerContext);
InstanceValidator v;
IEvaluationContext evaluationCtx = new NullEvaluationContext();
try {
v = new InstanceValidator(workerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new InstanceValidator(wrappedWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
String resourceName = determineResourceName(document);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, document, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
String resourceName = determineResourceName(document);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
String resourceName = json.get("resourceType").getAsString();
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
String resourceName = json.get("resourceType").getAsString();
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
if ("Binding has no source, so can't be checked".equals(next.getMessage())) {
messages.remove(i);
i--;
}
}
return messages;
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
if ("Binding has no source, so can't be checked".equals(next.getMessage())) {
messages.remove(i);
i--;
}
}
return messages;
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
public class NullEvaluationContext implements IEvaluationContext {
@Override
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException {
return null;
}
private class WorkerContextWrapper implements IWorkerContext {
private final HapiWorkerContext myWrap;
private final VersionConvertor_30_40 myConverter;
private volatile List<org.hl7.fhir.r4.model.StructureDefinition> myAllStructures;
@Override
public List<Base> executeFunction(Object theAppContext, String theFunctionName, List<List<Base>> theParameters) {
return null;
}
public WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext;
myConverter = new VersionConvertor_30_40();
}
@Override
public boolean log(String theArgument, List<Base> theFocus) {
return false;
}
@Override
public List<org.hl7.fhir.r4.model.MetadataResource> allConformanceResources() {
throw new UnsupportedOperationException();
}
@Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException {
return null;
}
@Override
public List<org.hl7.fhir.r4.model.StructureDefinition> allStructures() {
@Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException {
return null;
}
List<org.hl7.fhir.r4.model.StructureDefinition> retVal = myAllStructures;
if (retVal == null) {
retVal = new ArrayList<>();
for (StructureDefinition next : myWrap.allStructures()) {
try {
retVal.add(VersionConvertor_30_40.convertStructureDefinition(next));
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
myAllStructures = retVal;
}
@Override
public FunctionDetails resolveFunction(String theFunctionName) {
return null;
}
return retVal;
}
@Override
public Base resolveReference(Object theAppContext, String theUrl) {
return null;
}
@Override
public void cacheResource(org.hl7.fhir.r4.model.Resource res) throws FHIRException {
throw new UnsupportedOperationException();
}
}
private ValidationResult convertValidationResult(org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult theResult) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = VersionConvertor_30_40.convertConceptDefinitionComponent(theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
return retVal;
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
ValueSet convertedSource = null;
try {
convertedSource = VersionConvertor_30_40.convertValueSet(source);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical);
org.hl7.fhir.r4.model.ValueSet convertedResult = null;
if (expanded.getValueset() != null) {
try {
convertedResult = VersionConvertor_30_40.convertValueSet(expanded.getValueset());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
String error = expanded.getError();
ValueSetExpander.TerminologyServiceErrorClass result = null;
return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result);
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException {
throw new UnsupportedOperationException();
}
@Override
public org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandVS(org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException {
ValueSet.ConceptSetComponent convertedInc = null;
if (inc != null) {
try {
convertedInc = VersionConvertor_30_40.convertConceptSetComponent(inc);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
ValueSet.ValueSetExpansionComponent expansion = myWrap.expandVS(convertedInc, heirarchical);
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent retVal = null;
if (expansion != null) {
try {
retVal = VersionConvertor_30_40.convertValueSetExpansionComponent(expansion);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
return retVal;
}
@Override
public org.hl7.fhir.r4.model.CodeSystem fetchCodeSystem(String system) {
CodeSystem fetched = myWrap.fetchCodeSystem(system);
if (fetched == null) {
return null;
}
try {
return VersionConvertor_30_40.convertCodeSystem(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> class_, String uri) {
org.hl7.fhir.dstu3.model.Resource fetched;
switch (class_.getSimpleName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, uri);
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, uri);
break;
case "CodeSystem":
fetched = myWrap.fetchResource(CodeSystem.class, uri);
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, uri);
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + class_.getSimpleName());
}
if (fetched == null) {
return null;
}
try {
return (T) VersionConvertor_30_40.convertResource(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
@Override
public org.hl7.fhir.r4.model.Resource fetchResourceById(String type, String uri) {
throw new UnsupportedOperationException();
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
T retVal = fetchResource(class_, uri);
if (retVal == null) {
throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri);
}
return retVal;
}
@Override
public List<org.hl7.fhir.r4.model.ConceptMap> findMapsForSource(String url) {
throw new UnsupportedOperationException();
}
@Override
public String getAbbreviation(String name) {
return myWrap.getAbbreviation(name);
}
public VersionConvertor_30_40 getConverter() {
return myConverter;
}
@Override
public org.hl7.fhir.r4.model.ExpansionProfile getExpansionProfile() {
throw new UnsupportedOperationException();
}
@Override
public void setExpansionProfile(org.hl7.fhir.r4.model.ExpansionProfile expProfile) {
throw new UnsupportedOperationException();
}
@Override
public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) {
throw new UnsupportedOperationException();
}
@Override
public IParser getParser(ParserType type) {
throw new UnsupportedOperationException();
}
@Override
public IParser getParser(String type) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getResourceNames() {
return myWrap.getResourceNames();
}
@Override
public Set<String> getResourceNamesAsSet() {
return new HashSet<>(myWrap.getResourceNames());
}
@Override
public List<String> getTypeNames() {
return myWrap.getTypeNames();
}
@Override
public String getVersion() {
return myWrap.getVersion();
}
@Override
public boolean hasCache() {
return myWrap.hasCache();
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> class_, String uri) {
throw new UnsupportedOperationException();
}
@Override
public boolean isNoTerminologyServer() {
return myWrap.isNoTerminologyServer();
}
@Override
public IParser newJsonParser() {
throw new UnsupportedOperationException();
}
@Override
public IResourceValidator newValidator() throws FHIRException {
throw new UnsupportedOperationException();
}
@Override
public IParser newXmlParser() {
throw new UnsupportedOperationException();
}
@Override
public String oid2Uri(String code) {
return myWrap.oid2Uri(code);
}
@Override
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsSystem(String system) throws TerminologyServiceException {
return myWrap.supportsSystem(system);
}
@Override
public TranslationServices translator() {
throw new UnsupportedOperationException();
}
@Override
public Set<String> typeTails() {
return myWrap.typeTails();
}
@Override
public ValidationResult validateCode(String system, String code, String display) {
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(String system, String code, String display, org.hl7.fhir.r4.model.ValueSet vs) {
ValueSet convertedVs = null;
try {
if (vs != null) {
convertedVs = VersionConvertor_30_40.convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(org.hl7.fhir.r4.model.Coding code, org.hl7.fhir.r4.model.ValueSet vs) {
Coding convertedCode = null;
ValueSet convertedVs = null;
try {
if (code != null) {
convertedCode = VersionConvertor_30_40.convertCoding(code);
}
if (vs != null) {
convertedVs = VersionConvertor_30_40.convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(org.hl7.fhir.r4.model.CodeableConcept code, org.hl7.fhir.r4.model.ValueSet vs) {
CodeableConcept convertedCode = null;
ValueSet convertedVs = null;
try {
if (code != null) {
convertedCode = VersionConvertor_30_40.convertCodeableConcept(code);
}
if (vs != null) {
convertedVs = VersionConvertor_30_40.convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(String system, String code, String display, org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent vsi) {
ValueSet.ConceptSetComponent conceptSetComponent = null;
if (vsi != null) {
try {
conceptSetComponent = VersionConvertor_30_40.convertConceptSetComponent(vsi);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, conceptSetComponent);
return convertValidationResult(result);
}
}
}

View File

@ -241,7 +241,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
public class NullEvaluationContext implements IEvaluationContext {
public static class NullEvaluationContext implements IEvaluationContext {
@Override
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException {

View File

@ -1,20 +1,24 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.util.ObjectUtil;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.codec.binary.Base64;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
import javax.rmi.CORBA.Util;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.exceptions.*;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r4.elementmodel.*;
import org.hl7.fhir.r4.elementmodel.Element;
import org.hl7.fhir.r4.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4.elementmodel.*;
import org.hl7.fhir.r4.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r4.elementmodel.ParserBase.ValidationPolicy;
import org.hl7.fhir.r4.formats.FormatUtilities;
@ -24,38 +28,26 @@ import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.ElementDefinition.*;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemOptionComponent;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContext;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4.model.Questionnaire.*;
import org.hl7.fhir.r4.model.StructureDefinition.*;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4.utils.*;
import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.hl7.fhir.r4.utils.ToolingExtensions;
import org.hl7.fhir.r4.utils.ValidationProfileSet;
import org.hl7.fhir.r4.utils.ValidationProfileSet.ProfileRegistration;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
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;
import org.hl7.fhir.utilities.validation.ValidationMessage.*;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.util.ObjectUtil;
/**
@ -149,7 +141,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private IWorkerContext context;
private FHIRPathEngine fpe;
private FHIRPathEngine fpe;
// configuration items
private CheckDisplayOption checkDisplay;
@ -1193,7 +1185,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
List<Element> extensions = new ArrayList<Element>();
focus.getNamedChildren("extension", extensions);
if (fixed.getExtension().size() == 0) {
rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed");
rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed, as the specified fixed value doesn't contain any extensions");
} else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(),
"Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) {
for (Extension e : fixed.getExtension()) {
@ -1348,15 +1340,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String encoded = e.primitiveValue();
if (isNotBlank(encoded)) {
/*
* Note: Regex comes from: https://stackoverflow.com/questions/8571501/how-to-check-whether-the-string-is-base64-encoded-or-not
*
* Technically this is not bulletproof as some invalid base64 won't be caught,
* but I think it's good enough. The original code used Java8 Base64 decoder
* but I've replaced it with a regex for 2 reasons:
* 1. This code will run on any version of Java
* 2. This code doesn't actually decode, which is much easier on memory use for big payloads
*/
if (!encoded.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$")) {
int charCount = 0;
for (int i = 0; i < encoded.length(); i++) {
char nextChar = encoded.charAt(i);
if (Character.isWhitespace(nextChar)) {
continue;
}
if (Character.isLetterOrDigit(nextChar)) {
charCount++;
}
if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
charCount++;
}
}
if (charCount > 0 && charCount % 4 != 0) {
String value = encoded.length() < 100 ? encoded : "(snip)";
rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "The value \"{0}\" is not a valid Base64 value", value);
}
@ -2976,8 +2980,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
List<Element> unusedResources = new ArrayList<Element>();
boolean reverseLinksFound = false;
boolean reverseLinksFound;
do {
reverseLinksFound = false;
followResourceLinks(entries.get(0), visitedResources, candidateEntries, candidateResources, errors, stack);
unusedResources.clear();
unusedResources.addAll(candidateResources);
@ -3206,7 +3211,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " for profile " + profile.getUrl() + " and slicing is CLOSED"));
}
} else {
hint(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null), "Could not verify slice for profile " + profile.getUrl());
// Don't raise this if we're in an abstract profile, like Resource
if (!profile.getAbstract())
hint(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null), "Could not verify slice for profile " + profile.getUrl());
}
// TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements
boolean isXmlAttr = false;
@ -3251,7 +3258,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (ed.getMin() > 0) {
if (problematicPaths.contains(ed.getPath()))
hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(),
location + "': Unable to check minimum required (" + Integer.toString(ed.getMin()) + ") due to lack of slicing validation");
location + ": Unable to check minimum required (" + Integer.toString(ed.getMin()) + ") due to lack of slicing validation");
else
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(),
location + ": minimum required = " + Integer.toString(ed.getMin()) + ", but only found " + Integer.toString(count));

View File

@ -286,7 +286,6 @@ public class ResponseValidatingInterceptorDstu3Test {
* Ignored until #264 is fixed
*/
@Test
@Ignore
public void testSearchJsonInvalidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
@ -305,7 +304,6 @@ public class ResponseValidatingInterceptorDstu3Test {
ourLog.info("Response was:\n{}", responseContent);
Assert.assertEquals(422, status.getStatusLine().getStatusCode());
Assert.assertThat(status.toString(), Matchers.containsString("X-FHIR-Response-Validation"));
Assert.assertThat(responseContent, Matchers.containsString("<severity value=\"error\"/>"));
}
@ -384,7 +382,6 @@ public class ResponseValidatingInterceptorDstu3Test {
* Ignored until #264 is fixed
*/
@Test
@Ignore
public void testSearchXmlInvalidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
@ -403,7 +400,7 @@ public class ResponseValidatingInterceptorDstu3Test {
ourLog.info("Response was:\n{}", responseContent);
Assert.assertEquals(422, status.getStatusLine().getStatusCode());
Assert.assertThat(status.toString(), Matchers.containsString("X-FHIR-Response-Validation"));
Assert.assertThat(responseContent, Matchers.containsString("<severity value=\"error\"/>"));
}
@Test

View File

@ -20,6 +20,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
@ -283,7 +284,6 @@ public class ResponseValidatingInterceptorR4Test {
* Ignored until #264 is fixed
*/
@Test
@Ignore
public void testSearchJsonInvalidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
@ -302,7 +302,6 @@ public class ResponseValidatingInterceptorR4Test {
ourLog.info("Response was:\n{}", responseContent);
assertEquals(422, status.getStatusLine().getStatusCode());
assertThat(status.toString(), containsString("X-FHIR-Response-Validation"));
assertThat(responseContent, containsString("<severity value=\"error\"/>"));
}
@ -381,7 +380,6 @@ public class ResponseValidatingInterceptorR4Test {
* Ignored until #264 is fixed
*/
@Test
@Ignore
public void testSearchXmlInvalidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
@ -400,7 +398,7 @@ public class ResponseValidatingInterceptorR4Test {
ourLog.info("Response was:\n{}", responseContent);
assertEquals(422, status.getStatusLine().getStatusCode());
assertThat(status.toString(), containsString("X-FHIR-Response-Validation"));
Assert.assertThat(responseContent, Matchers.containsString("<severity value=\"error\"/>"));
}
@Test

View File

@ -54,7 +54,10 @@ public class FhirInstanceValidatorDstu3Test {
private Map<String, ValueSetExpansionComponent> mySupportedCodeSystemsForExpansion;
private FhirValidator myVal;
private ArrayList<String> myValidConcepts;
private Set<String> myValidSystems = new HashSet<String>();
private Set<String> myValidSystems = new HashSet<>();
private HashMap<String, StructureDefinition> myStructureDefinitions;
private HashMap<String, CodeSystem> myCodeSystems;
private HashMap<String, ValueSet> myValueSets;
private void addValidConcept(String theSystem, String theCode) {
myValidSystems.add(theSystem);
@ -74,13 +77,13 @@ public class FhirInstanceValidatorDstu3Test {
myVal.registerValidatorModule(myInstanceVal);
mySupportedCodeSystemsForExpansion = new HashMap<String, ValueSet.ValueSetExpansionComponent>();
mySupportedCodeSystemsForExpansion = new HashMap<>();
myValidConcepts = new ArrayList<String>();
myValidConcepts = new ArrayList<>();
when(myMockSupport.expandValueSet(any(FhirContext.class), any(ConceptSetComponent.class))).thenAnswer(new Answer<ValueSetExpansionComponent>() {
@Override
public ValueSetExpansionComponent answer(InvocationOnMock theInvocation) throws Throwable {
public ValueSetExpansionComponent answer(InvocationOnMock theInvocation) {
ConceptSetComponent arg = (ConceptSetComponent) theInvocation.getArguments()[0];
ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getSystem());
if (retVal == null) {
@ -92,7 +95,7 @@ public class FhirInstanceValidatorDstu3Test {
});
when(myMockSupport.isCodeSystemSupported(any(FhirContext.class), any(String.class))).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock theInvocation) throws Throwable {
public Boolean answer(InvocationOnMock theInvocation) {
boolean retVal = myValidSystems.contains(theInvocation.getArguments()[1]);
ourLog.debug("isCodeSystemSupported({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal});
return retVal;
@ -101,20 +104,36 @@ public class FhirInstanceValidatorDstu3Test {
when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class))).thenAnswer(new Answer<IBaseResource>() {
@Override
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IBaseResource retVal;
IBaseResource retVal = null;
Class<?> type = (Class<?>) theInvocation.getArguments()[1];
String id = (String) theInvocation.getArguments()[2];
if ("Questionnaire/q_jon".equals(id)) {
retVal = ourCtx.newJsonParser().parseResource(IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/q_jon.json")));
} else {
retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1], id);
if (StructureDefinition.class.equals(type)) {
retVal = myStructureDefinitions.get(id);
}
if (ValueSet.class.equals(type)) {
retVal = myValueSets.get(id);
}
if (CodeSystem.class.equals(type)) {
retVal = myCodeSystems.get(id);
}
if (retVal == null) {
retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1], id);
}
}
if (retVal == null) {
ourLog.info("fetchResource({}, {}) : {}", new Object[]{type, id, retVal});
}
ourLog.debug("fetchResource({}, {}) : {}", new Object[]{theInvocation.getArguments()[1], id, retVal});
return retVal;
}
});
when(myMockSupport.validateCode(any(FhirContext.class), any(String.class), any(String.class), any(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
@Override
public CodeValidationResult answer(InvocationOnMock theInvocation) throws Throwable {
public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0];
String system = (String) theInvocation.getArguments()[1];
String code = (String) theInvocation.getArguments()[2];
@ -124,29 +143,36 @@ public class FhirInstanceValidatorDstu3Test {
} else {
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, (String) theInvocation.getArguments()[2]);
}
ourLog.debug("validateCode({}, {}, {}) : {}", new Object[]{system, code, (String) theInvocation.getArguments()[2], retVal});
ourLog.debug("validateCode({}, {}, {}) : {}", new Object[]{system, code, theInvocation.getArguments()[2], retVal});
return retVal;
}
});
when(myMockSupport.fetchCodeSystem(any(FhirContext.class), any(String.class))).thenAnswer(new Answer<CodeSystem>() {
@Override
public CodeSystem answer(InvocationOnMock theInvocation) throws Throwable {
public CodeSystem answer(InvocationOnMock theInvocation) {
CodeSystem retVal = myDefaultValidationSupport.fetchCodeSystem((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]);
ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{(String) theInvocation.getArguments()[1], retVal});
ourLog.debug("fetchCodeSystem({}) : {}", new Object[]{theInvocation.getArguments()[1], retVal});
return retVal;
}
});
myStructureDefinitions = new HashMap<>();
myValueSets = new HashMap<>();
myCodeSystems = new HashMap<>();
when(myMockSupport.fetchStructureDefinition(any(FhirContext.class), any(String.class))).thenAnswer(new Answer<StructureDefinition>() {
@Override
public StructureDefinition answer(InvocationOnMock theInvocation) throws Throwable {
StructureDefinition retVal = myDefaultValidationSupport.fetchStructureDefinition((FhirContext) theInvocation.getArguments()[0], (String) theInvocation.getArguments()[1]);
ourLog.debug("fetchStructureDefinition({}) : {}", new Object[]{(String) theInvocation.getArguments()[1], retVal});
public StructureDefinition answer(InvocationOnMock theInvocation) {
String url = (String) theInvocation.getArguments()[1];
StructureDefinition retVal = myStructureDefinitions.get(url);
if (retVal == null) {
retVal = myDefaultValidationSupport.fetchStructureDefinition((FhirContext) theInvocation.getArguments()[0], url);
}
ourLog.info("fetchStructureDefinition({}) : {}", new Object[]{url, retVal});
return retVal;
}
});
when(myMockSupport.fetchAllStructureDefinitions(any(FhirContext.class))).thenAnswer(new Answer<List<StructureDefinition>>() {
@Override
public List<StructureDefinition> answer(InvocationOnMock theInvocation) throws Throwable {
public List<StructureDefinition> answer(InvocationOnMock theInvocation) {
List<StructureDefinition> retVal = myDefaultValidationSupport.fetchAllStructureDefinitions((FhirContext) theInvocation.getArguments()[0]);
ourLog.debug("fetchAllStructureDefinitions()", new Object[]{});
return retVal;
@ -159,6 +185,10 @@ public class FhirInstanceValidatorDstu3Test {
return theLocationLine != null ? theLocationLine.toString() : "";
}
private String loadResource(String theFileName) throws IOException {
return IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream(theFileName));
}
private List<SingleValidationMessage> logResultsAndReturnAll(ValidationResult theOutput) {
List<SingleValidationMessage> retVal = new ArrayList<SingleValidationMessage>();
@ -219,6 +249,18 @@ public class FhirInstanceValidatorDstu3Test {
}
@Test
public void testGoal() {
Goal goal = new Goal();
goal.setSubject(new Reference("Patient/123"));
goal.setDescription(new CodeableConcept().addCoding(new Coding("http://foo", "some other goal", "")));
goal.setStatus(Goal.GoalStatus.INPROGRESS);
ValidationResult results = myVal.validateWithResult(goal);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results);
assertEquals(0, outcome.size());
}
@Test
public void testIsNoTerminologyChecks() {
assertFalse(myInstanceVal.isNoTerminologyChecks());
@ -226,6 +268,51 @@ public class FhirInstanceValidatorDstu3Test {
assertTrue(myInstanceVal.isNoTerminologyChecks());
}
/**
* See #824
*/
@Test
public void testValidateBadCodeForRequiredBinding() throws IOException {
StructureDefinition fiphrPefStu3 = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-profile-fiphr-pef-stu3.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-pef-stu3", fiphrPefStu3);
StructureDefinition fiphrDevice = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-fiphr-device.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-device", fiphrDevice);
StructureDefinition fiphrCreatingApplication = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-creatingapplication.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-ext-creatingapplication", fiphrCreatingApplication);
StructureDefinition fiphrBoolean = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-fiphr-boolean.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-boolean", fiphrBoolean);
StructureDefinition medContext = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-fiphr-medicationcontext.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-medicationcontext", medContext);
StructureDefinition fiphrVitalSigns = ourCtx.newJsonParser().parseResource(StructureDefinition.class, loadResource("/dstu3/bug824-fiphr-vitalsigns-stu3.json"));
myStructureDefinitions.put("http://phr.kanta.fi/StructureDefinition/fiphr-vitalsigns-stu3", fiphrVitalSigns);
CodeSystem csObservationMethod = ourCtx.newJsonParser().parseResource(CodeSystem.class, loadResource("/dstu3/bug824-fhirphr-cs-observationmethod.json"));
myCodeSystems.put("http://phr.kanta.fi/fiphr-cs-observationmethod", csObservationMethod);
ValueSet vsObservationMethod = ourCtx.newJsonParser().parseResource(ValueSet.class, loadResource("/dstu3/bug824-vs-observaionmethod.json"));
myValueSets.put("http://phr.kanta.fi/ValueSet/fiphr-vs-observationmethod", vsObservationMethod);
ValueSet vsVitalSigns = ourCtx.newJsonParser().parseResource(ValueSet.class, loadResource("/dstu3/bug824-vs-vitalsigns.json"));
myValueSets.put("http://phr.kanta.fi/ValueSet/fiphr-vs-vitalsigns", vsVitalSigns);
ValueSet vsMedicationContext = ourCtx.newJsonParser().parseResource(ValueSet.class, loadResource("/dstu3/bug824-vs-medicationcontext.json"));
myValueSets.put("http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext", vsMedicationContext);
ValueSet vsConfidentiality = ourCtx.newJsonParser().parseResource(ValueSet.class, loadResource("/dstu3/bug824-vs-confidentiality.json"));
myValueSets.put("http://phr.kanta.fi/ValueSet/fiphr-vs-confidentiality", vsConfidentiality);
String input = loadResource("/dstu3/bug824-resource.json");
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> issues = logResultsAndReturnNonInformationalOnes(output);
assertThat(issues.toString(), containsString("None of the codes provided are in the value set http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext"));
}
@Test
public void testValidateBigRawJsonResource() throws Exception {
InputStream stream = FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/conformance.json.gz");
@ -283,6 +370,15 @@ public class FhirInstanceValidatorDstu3Test {
ourLog.info("Validated the following:\n{}", ids);
}
@Test
public void testValidateBundleWithNoType() throws Exception {
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/dstu3/bundle-with-no-type.json"), "UTF-8");
ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output);
assertThat(output.getMessages().toString(), containsString("Element 'Bundle.type': minimum required = 1"));
}
@Test
@Ignore
public void testValidateBundleWithObservations() throws Exception {
@ -336,15 +432,6 @@ public class FhirInstanceValidatorDstu3Test {
assertTrue(output.isSuccessful());
}
@Test
public void testValidateBundleWithNoType() throws Exception {
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/dstu3/bundle-with-no-type.json"), "UTF-8");
ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output);
assertThat(output.getMessages().toString(), containsString("Element 'Bundle.type': minimum required = 1"));
}
/**
* See #739
*/
@ -360,7 +447,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test
public void testValidateQuestionnaireResponse() throws IOException {
String input = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/qr_jon.xml"));
String input = loadResource("/qr_jon.xml");
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
@ -506,12 +593,11 @@ public class FhirInstanceValidatorDstu3Test {
@Test
public void testValidateRawXmlResourceWithEmptyPrimitive() {
// @formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\"><name><given/></name></Patient>";
// @formatter:on
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 2, output.getMessages().size());
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output.toOperationOutcome()));
assertEquals(output.toString(), 3, output.getMessages().size());
assertThat(output.getMessages().get(0).getMessage(), containsString("Element must have some content"));
assertThat(output.getMessages().get(1).getMessage(), containsString("primitive types must have a value or must have child extensions"));
}
@ -575,7 +661,7 @@ public class FhirInstanceValidatorDstu3Test {
* A reference with only an identifier should be valid
*/
@Test
public void testValidateReferenceWithDisplayValid() throws Exception {
public void testValidateReferenceWithDisplayValid() {
Patient p = new Patient();
p.getManagingOrganization().setDisplay("HELLO");
@ -588,7 +674,7 @@ public class FhirInstanceValidatorDstu3Test {
* A reference with only an identifier should be valid
*/
@Test
public void testValidateReferenceWithIdentifierValid() throws Exception {
public void testValidateReferenceWithIdentifierValid() {
Patient p = new Patient();
p.getManagingOrganization().getIdentifier().setSystem("http://acme.org");
p.getManagingOrganization().getIdentifier().setValue("foo");
@ -677,7 +763,7 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.toString(), containsString("Element 'Observation.subject': minimum required = 1, but only found 0"));
assertThat(errors.toString(), containsString("Element 'Observation.context: max allowed = 0, but found 1"));
assertThat(errors.toString(), containsString("Element 'Observation.context': max allowed = 0, but found 1"));
assertThat(errors.toString(), containsString("Element 'Observation.device': minimum required = 1, but only found 0"));
assertThat(errors.toString(), containsString(""));
}
@ -856,7 +942,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test
@Ignore
public void testValidateStructureDefinition() throws IOException {
String input = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/sdc-questionnaire.profile.xml"));
String input = loadResource("/sdc-questionnaire.profile.xml");
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
@ -877,18 +963,6 @@ public class FhirInstanceValidatorDstu3Test {
}
@Test
public void testGoal() {
Goal goal = new Goal();
goal.setSubject(new Reference("Patient/123"));
goal.setDescription(new CodeableConcept().addCoding(new Coding("http://foo","some other goal","")));
goal.setStatus(Goal.GoalStatus.INPROGRESS);
ValidationResult results = myVal.validateWithResult(goal);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results);
assertEquals(0, outcome.size());
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();

View File

@ -170,7 +170,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("minimum required = 1, but only found 0 - QuestionnaireResponse.item"));
assertThat(errors.toString(), containsString("No LinkId, so can't be validated"));
}
@Test

View File

@ -287,12 +287,11 @@ public class ResourceValidatorDstu3Test {
* TODO: re-enable this
*/
@Test
@Ignore
public void testValidateQuestionnaireWithCanonicalUrl() {
String input = "{\n" +
" \"resourceType\": \"Questionnaire\",\n" +
" \"url\": \"http://some.example.url\",\n" +
" \"status\": \"published\",\n" +
" \"status\": \"active\",\n" +
" \"subjectType\": [\n" +
" \"Patient\"\n" +
" ],\n" +
@ -377,16 +376,15 @@ public class ResourceValidatorDstu3Test {
PatientProfileDstu3 myPatient = new PatientProfileDstu3();
myPatient.setId("1");
myPatient.setColorPrimary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setCode("furry-grey")));
myPatient.setColorSecondary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setSystem("furry-white")));
myPatient.setColorSecondary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setCode("furry-white")));
myPatient.setOwningOrganization(new Reference("Organization/2.25.79433498044103547197447759549862032393"));
myPatient.addName().setFamily("FamilyName");
myPatient.addExtension().setUrl("http://foo.com/example").setValue(new StringType("String Extension"));
IParser p = ourCtx.newJsonParser().setPrettyPrint(true);
String messageString = p.encodeResourceToString(myPatient);
ourLog.info(messageString);
// ourLog.info(messageString);
//@formatter:off
assertThat(messageString, stringContainsInOrder(
"meta",
"String Extension",
@ -399,7 +397,6 @@ public class ResourceValidatorDstu3Test {
"extension",
"meta"
)));
//@formatter:on
FhirValidator val = ourCtx.newValidator();
val.registerValidatorModule(new SchemaBaseValidator(ourCtx));
@ -426,7 +423,7 @@ public class ResourceValidatorDstu3Test {
PatientProfileDstu3 myPatient = new PatientProfileDstu3();
myPatient.setId("1");
myPatient.setColorPrimary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setCode("furry-grey")));
myPatient.setColorSecondary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setSystem("furry-white")));
myPatient.setColorSecondary(new CodeableConcept().addCoding(new Coding().setSystem("http://example.com#animalColor").setCode("furry-white")));
myPatient.setOwningOrganization(new Reference("Organization/2.25.79433498044103547197447759549862032393"));
myPatient.addName().setFamily("FamilyName");
myPatient.addExtension().setUrl("http://foo.com/example").setValue(new StringType("String Extension"));

View File

@ -17,6 +17,7 @@ import java.io.InputStream;
import java.util.*;
import java.util.zip.GZIPInputStream;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
@ -247,6 +248,15 @@ public class FhirInstanceValidatorR4Test {
}
@Test
public void testLargeBase64() throws IOException {
String input = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/r4/diagnosticreport-example-gingival-mass.json"), Constants.CHARSET_UTF8);
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, errors.size());
}
@Test @Ignore
public void testValidateDocument() throws Exception {
String vsContents = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/sample-document.xml"), "UTF-8");

View File

@ -0,0 +1,941 @@
{
"resourceType": "StructureDefinition",
"id": "fiphr-ext-creatingapplication",
"meta": {
"lastUpdated": "2017-09-20T13:05:42.786+00:00"
},
"url": "http://phr.kanta.fi/StructureDefinition/fiphr-ext-creatingapplication",
"version": "0.1",
"name": "Finnish PHR Application Information extension",
"title": "Finnish PHR Application Information extension",
"status": "draft",
"experimental": true,
"date": "2017-09-12T05:53:10.958+00:00",
"publisher": "Kela",
"description": "Information about the application that created the resource instance",
"purpose": "Finnish PHR extension structure for information about application that has created the reource. Extension shall be included in all resources that are stored in PHR, except contained resources.",
"fhirVersion": "3.0.1",
"kind": "complex-type",
"abstract": false,
"contextType": "resource",
"context": [
"StructureDefinition"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"snapshot": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Application identification",
"definition": "Application identification information. SHALL have same values that has been registered for application in PHR application catalog. ",
"min": 0,
"max": "*",
"base": {
"path": "Element",
"min": 0,
"max": "*"
},
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.id",
"path": "Extension.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": 0,
"max": "1",
"base": {
"path": "Element.id",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": 0,
"max": "*",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:name",
"path": "Extension.extension",
"sliceName": "name",
"short": "Application name",
"definition": "Application name registered in PHR cataloque.",
"comment": ".",
"alias": [
"extensions",
"user content"
],
"min": 1,
"max": "1",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:name.id",
"path": "Extension.extension.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": 0,
"max": "1",
"base": {
"path": "Element.id",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension:name.extension",
"path": "Extension.extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": 0,
"max": "*",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:name.url",
"path": "Extension.extension.url",
"representation": [
"xmlAttr"
],
"short": "identifies the meaning of the extension",
"definition": "Source of the definition for the extension code - a logical name or a URL.",
"comment": "The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.",
"min": 1,
"max": "1",
"base": {
"path": "Extension.url",
"min": 1,
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "name",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:name.value[x]:valueString",
"path": "Extension.extension.valueString",
"sliceName": "valueString",
"short": "Value of extension",
"definition": "Value of extension - may be a resource or one of a constrained set of the data types (see Extensibility in the spec for list).",
"comment": "A stream of bytes, base64 encoded",
"min": 1,
"max": "1",
"base": {
"path": "Extension.value[x]",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:applicationId",
"path": "Extension.extension",
"sliceName": "applicationId",
"short": "Application identifier",
"definition": "Application id. A unique identifier, assigned to the client by Kela in registeration process",
"comment": ".",
"alias": [
"extensions",
"user content"
],
"min": 1,
"max": "1",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:applicationId.id",
"path": "Extension.extension.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": 0,
"max": "1",
"base": {
"path": "Element.id",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension:applicationId.extension",
"path": "Extension.extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": 0,
"max": "*",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:applicationId.url",
"path": "Extension.extension.url",
"representation": [
"xmlAttr"
],
"short": "identifies the meaning of the extension",
"definition": "Source of the definition for the extension code - a logical name or a URL.",
"comment": "The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.",
"min": 1,
"max": "1",
"base": {
"path": "Extension.url",
"min": 1,
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "applicationId",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.extension:applicationId.value[x]:valueString",
"path": "Extension.extension.valueString",
"sliceName": "valueString",
"short": "Value of extension",
"definition": "Value of extension - may be a resource or one of a constrained set of the data types (see Extensibility in the spec for list).",
"comment": "A stream of bytes, base64 encoded",
"min": 1,
"max": "1",
"base": {
"path": "Extension.value[x]",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.url",
"path": "Extension.url",
"representation": [
"xmlAttr"
],
"short": "identifies the meaning of the extension",
"definition": "Source of the definition for the extension code - a logical name or a URL.",
"comment": "The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.",
"min": 1,
"max": "1",
"base": {
"path": "Extension.url",
"min": 1,
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-ext-creatingapplication",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.value[x]",
"path": "Extension.value[x]",
"short": "Value of extension",
"definition": "Value of extension - may be a resource or one of a constrained set of the data types (see Extensibility in the spec for list).",
"comment": "A stream of bytes, base64 encoded",
"min": 0,
"max": "0",
"base": {
"path": "Extension.value[x]",
"min": 0,
"max": "1"
},
"type": [
{
"code": "base64Binary"
},
{
"code": "boolean"
},
{
"code": "code"
},
{
"code": "date"
},
{
"code": "dateTime"
},
{
"code": "decimal"
},
{
"code": "id"
},
{
"code": "instant"
},
{
"code": "integer"
},
{
"code": "markdown"
},
{
"code": "oid"
},
{
"code": "positiveInt"
},
{
"code": "string"
},
{
"code": "time"
},
{
"code": "unsignedInt"
},
{
"code": "uri"
},
{
"code": "Address"
},
{
"code": "Age"
},
{
"code": "Annotation"
},
{
"code": "Attachment"
},
{
"code": "CodeableConcept"
},
{
"code": "Coding"
},
{
"code": "ContactPoint"
},
{
"code": "Count"
},
{
"code": "Distance"
},
{
"code": "Duration"
},
{
"code": "HumanName"
},
{
"code": "Identifier"
},
{
"code": "Money"
},
{
"code": "Period"
},
{
"code": "Quantity"
},
{
"code": "Range"
},
{
"code": "Ratio"
},
{
"code": "Reference"
},
{
"code": "SampledData"
},
{
"code": "Signature"
},
{
"code": "Timing"
},
{
"code": "Meta"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
}
]
},
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Application identification",
"definition": "Application identification information. SHALL have same values that has been registered for application in PHR application catalog. "
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"rules": "open"
}
},
{
"id": "Extension.extension:name",
"path": "Extension.extension",
"sliceName": "name",
"short": "Application name",
"definition": "Application name registered in PHR cataloque.",
"comment": ".",
"min": 1,
"max": "1"
},
{
"id": "Extension.extension:name.url",
"path": "Extension.extension.url",
"fixedUri": "name"
},
{
"id": "Extension.extension:name.value[x]:valueString",
"path": "Extension.extension.valueString",
"sliceName": "valueString",
"min": 1,
"type": [
{
"code": "string"
}
]
},
{
"id": "Extension.extension:applicationId",
"path": "Extension.extension",
"sliceName": "applicationId",
"short": "Application identifier",
"definition": "Application id. A unique identifier, assigned to the client by Kela in registeration process",
"comment": ".",
"min": 1,
"max": "1"
},
{
"id": "Extension.extension:applicationId.url",
"path": "Extension.extension.url",
"fixedUri": "applicationId"
},
{
"id": "Extension.extension:applicationId.value[x]:valueString",
"path": "Extension.extension.valueString",
"sliceName": "valueString",
"min": 1,
"type": [
{
"code": "string"
}
]
},
{
"id": "Extension.url",
"path": "Extension.url",
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-ext-creatingapplication"
},
{
"id": "Extension.value[x]",
"path": "Extension.value[x]",
"max": "0"
}
]
}
}

View File

@ -0,0 +1,36 @@
{
"resourceType":"CodeSystem",
"id":"fiphr-cs-observationmethod",
"meta":{
"profile": [
"http://hl7.org/fhir/StructureDefinition/shareablecodesystem"
]
},
"url":"http://phr.kanta.fi/fiphr-cs-observationmethod",
"version":"0.01",
"name":"Code System Finnish PHR Observation Method ",
"status":"draft",
"experimental":true,
"date":"2017-05-22T06:00:00+00:00",
"publisher":"Kela ",
"description":"Finnish PHR Observation Method codes.",
"caseSensitive":true,
"content":"complete",
"concept": [
{
"code":"1",
"display":"Method 1",
"definition":"Method 1",
"designation": [
{
"language":"fi",
"value":"Metodi 1"
},
{
"language":"sv",
"value":"Metod 1"
}
]
}
]
}

View File

@ -0,0 +1,286 @@
{
"resourceType": "StructureDefinition",
"id": "fiphr-boolean",
"meta": {
"lastUpdated": "2017-09-05T12:48:55.148+00:00"
},
"url": "http://phr.kanta.fi/StructureDefinition/fiphr-boolean",
"version": "0.05",
"name": "Finnish PHR boolean extension",
"title": "Finnish PHR boolean extension",
"status": "draft",
"experimental": true,
"date": "2017-08-18T07:54:02.783+00:00",
"publisher": "Kela",
"description": "Finnish PHR extension structure to be used in boolean type extension element in profiles.",
"fhirVersion": "3.0.1",
"kind": "primitive-type",
"abstract": false,
"contextType": "resource",
"context": [
"StructureDefinition"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"snapshot": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Finnish PHR boolean extension",
"definition": "Finnish PHR extension structure to be used in boolean type extension element in profiles.",
"min": 0,
"max": "*",
"base": {
"path": "Element",
"min": 0,
"max": "*"
},
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.id",
"path": "Extension.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": 0,
"max": "1",
"base": {
"path": "Element.id",
"min": 0,
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": 0,
"max": "*",
"base": {
"path": "Element.extension",
"min": 0,
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.url",
"path": "Extension.url",
"representation": [
"xmlAttr"
],
"short": "identifies the meaning of the extension",
"definition": "Source of the definition for the extension code - a logical name or a URL.",
"comment": "The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.",
"min": 1,
"max": "1",
"base": {
"path": "Extension.url",
"min": 1,
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-boolean",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.value[x]:valueBoolean",
"path": "Extension.valueBoolean",
"sliceName": "valueBoolean",
"short": "Value of extension",
"definition": "Value of extension - may be a resource or one of a constrained set of the data types (see Extensibility in the spec for list).",
"comment": "A stream of bytes, base64 encoded",
"min": 0,
"max": "1",
"base": {
"path": "Extension.value[x]",
"min": 0,
"max": "1"
},
"type": [
{
"code": "boolean"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
}
]
},
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Finnish PHR boolean extension",
"definition": "Finnish PHR extension structure to be used in boolean type extension element in profiles."
},
{
"id": "Extension.url",
"path": "Extension.url",
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-boolean"
},
{
"id": "Extension.value[x]:valueBoolean",
"path": "Extension.valueBoolean",
"sliceName": "valueBoolean",
"type": [
{
"code": "boolean"
}
]
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,890 @@
{
"resourceType": "StructureDefinition",
"id": "fiphr-medicationcontext",
"meta": {
"lastUpdated": "2017-09-01T05:15:12.474+00:00"
},
"url": "http://phr.kanta.fi/StructureDefinition/fiphr-medicationcontext",
"version": "0.05",
"name": "Finnish PHR medicationcontext extension",
"title": "Finnish PHR medicationcontext extension",
"status": "draft",
"experimental": true,
"date": "2017-08-31T06:09:42.264+00:00",
"publisher": "Kela",
"description": "Finnish PHR Medication Context Extension to express observation timing related to medication.",
"fhirVersion": "3.0.1",
"kind": "complex-type",
"abstract": false,
"contextType": "resource",
"context": [
"Observation"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"snapshot": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Finnish PHR medicationcontext extension",
"definition": "Finnish PHR Medication Context Extension to express observation timing related to medication.",
"min": "0",
"max": "*",
"base": {
"path": "Element",
"min": "0",
"max": "*"
},
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.id",
"path": "Extension.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": "0",
"max": "1",
"base": {
"path": "Element.id",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": "0",
"max": "*",
"base": {
"path": "Element.extension",
"min": "0",
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.url",
"path": "Extension.url",
"representation": [
"xmlAttr"
],
"short": "identifies the meaning of the extension",
"definition": "Source of the definition for the extension code - a logical name or a URL.",
"comment": "The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.",
"min": "1",
"max": "1",
"base": {
"path": "Extension.url",
"min": "1",
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-medicationcontext",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept",
"path": "Extension.valueCodeableConcept",
"sliceName": "valueCodeableConcept",
"short": "Value of extension",
"definition": "Value of extension - may be a resource or one of a constrained set of the data types (see Extensibility in the spec for list).",
"comment": "A stream of bytes, base64 encoded",
"min": "0",
"max": "1",
"base": {
"path": "Extension.value[x]",
"min": "0",
"max": "1"
},
"type": [
{
"code": "CodeableConcept"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"binding": {
"strength": "required",
"valueSetUri": "http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext"
},
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.id",
"path": "Extension.valueCodeableConcept.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": "0",
"max": "1",
"base": {
"path": "Element.id",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.extension",
"path": "Extension.valueCodeableConcept.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": "0",
"max": "*",
"base": {
"path": "Element.extension",
"min": "0",
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding",
"path": "Extension.valueCodeableConcept.coding",
"short": "Code defined by a terminology system",
"definition": "A reference to a code defined by a terminology system.",
"comment": "Codes may be defined very casually in enumerations, or code lists, up to very formal definitions such as SNOMED CT - see the HL7 v3 Core Principles for more information. Ordering of codings is undefined and SHALL NOT be used to infer meaning. Generally, at most only one of the coding values will be labeled as UserSelected = true.",
"requirements": "Allows for translations and alternate encodings within a code system. Also supports communication of the same instance to systems requiring different encodings.",
"min": "0",
"max": "*",
"base": {
"path": "CodeableConcept.coding",
"min": "0",
"max": "*"
},
"type": [
{
"code": "Coding"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "CE/CNE/CWE subset one of the sets of component 1-3 or 4-6"
},
{
"identity": "rim",
"map": "CV"
},
{
"identity": "orim",
"map": "fhir:Coding rdfs:subClassOf dt:CDCoding"
},
{
"identity": "v2",
"map": "C*E.1-8, C*E.10-22"
},
{
"identity": "rim",
"map": "union(., ./translation)"
},
{
"identity": "orim",
"map": "fhir:CodeableConcept.coding rdfs:subPropertyOf dt:CD.coding"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding.id",
"path": "Extension.valueCodeableConcept.coding.id",
"representation": [
"xmlAttr"
],
"short": "xml:id (or equivalent in JSON)",
"definition": "unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"min": "0",
"max": "1",
"base": {
"path": "Element.id",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding.extension",
"path": "Extension.valueCodeableConcept.coding.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"description": "Extensions are always sliced by (at least) url",
"rules": "open"
},
"short": "Additional Content defined by implementations",
"definition": "May be used to represent additional information that is not part of the basic definition of the element. In order to make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
"comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
"alias": [
"extensions",
"user content"
],
"min": "0",
"max": "*",
"base": {
"path": "Element.extension",
"min": "0",
"max": "*"
},
"type": [
{
"code": "Extension"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
},
{
"key": "ext-1",
"severity": "error",
"human": "Must have either extensions or value[x], not both",
"expression": "extension.exists() != value.exists()",
"xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])"
}
],
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "rim",
"map": "N/A"
}
]
},
{
"id": "valueCodeableConcept:valueCodeableConcept.coding.system",
"path": "Extension.valueCodeableConcept.coding.system",
"short": "Identity of the terminology system",
"definition": "The identification of the code system that defines the meaning of the symbol in the code.",
"comment": "The URI may be an OID (urn:oid:...) or a UUID (urn:uuid:...). OIDs and UUIDs SHALL be references to the HL7 OID registry. Otherwise, the URI should come from HL7's list of FHIR defined special URIs or it should de-reference to some definition that establish the system clearly and unambiguously.",
"requirements": "Need to be unambiguous about the source of the definition of the symbol.",
"min": "1",
"max": "1",
"base": {
"path": "Coding.system",
"min": "0",
"max": "1"
},
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://phr.kanta.fi/fiphr-cs-medicationcontext",
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "C*E.3"
},
{
"identity": "rim",
"map": "./codeSystem"
},
{
"identity": "orim",
"map": "fhir:Coding.system rdfs:subPropertyOf dt:CDCoding.codeSystem"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding.version",
"path": "Extension.valueCodeableConcept.coding.version",
"short": "Version of the system - if relevant",
"definition": "The version of the code system which was used when choosing this code. Note that a well-maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured. and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.",
"comment": "Where the terminology does not clearly define what string should be used to identify code system versions, the recommendation is to use the date (expressed in FHIR date format) on which that version was officially published as the version date.",
"min": "0",
"max": "1",
"base": {
"path": "Coding.version",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "C*E.7"
},
{
"identity": "rim",
"map": "./codeSystemVersion"
},
{
"identity": "orim",
"map": "fhir:Coding.version rdfs:subPropertyOf dt:CDCoding.codeSystemVersion"
}
]
},
{
"id": "valueCodeableConcept:valueCodeableConcept.coding.code",
"path": "Extension.valueCodeableConcept.coding.code",
"short": "Symbol in syntax defined by the system",
"definition": "A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post-coordination).",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"requirements": "Need to refer to a particular code in the system.",
"min": "1",
"max": "1",
"base": {
"path": "Coding.code",
"min": "0",
"max": "1"
},
"type": [
{
"code": "code"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "C*E.1"
},
{
"identity": "rim",
"map": "./code"
},
{
"identity": "orim",
"map": "fhir:Coding.code rdfs:subPropertyOf dt:CDCoding.code"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding.display",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable",
"valueBoolean": true
}
],
"path": "Extension.valueCodeableConcept.coding.display",
"short": "Representation defined by the system",
"definition": "A representation of the meaning of the code in the system, following the rules of the system.",
"comment": "Note that FHIR strings may not exceed 1MB in size",
"requirements": "Need to be able to carry a human-readable meaning of the code for readers that do not know the system.",
"min": "0",
"max": "1",
"base": {
"path": "Coding.display",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "C*E.2 - but note this is not well followed"
},
{
"identity": "rim",
"map": "CV.displayName"
},
{
"identity": "orim",
"map": "fhir:Coding.display rdfs:subPropertyOf dt:CDCoding.displayName"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.coding.userSelected",
"path": "Extension.valueCodeableConcept.coding.userSelected",
"short": "If this coding was chosen directly by the user",
"definition": "Indicates that this coding was chosen by a user directly - i.e. off a pick list of available items (codes or displays).",
"comment": "Amongst a set of alternatives, a directly chosen code is the most appropriate starting point for new translations. There is some ambiguity about what exactly 'directly chosen' implies, and trading partner agreement may be needed to clarify the use of this element and its consequences more completely.",
"requirements": "This has been identified as a clinical safety criterium - that this exact system/code pair was chosen explicitly, rather than inferred by the system based on some rules or language processing.",
"min": "0",
"max": "1",
"base": {
"path": "Coding.userSelected",
"min": "0",
"max": "1"
},
"type": [
{
"code": "boolean"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "Sometimes implied by being first"
},
{
"identity": "rim",
"map": "CD.codingRationale"
},
{
"identity": "orim",
"map": "fhir:Coding.userSelected fhir:mapsTo dt:CDCoding.codingRationale. fhir:Coding.userSelected fhir:hasMap fhir:Coding.userSelected.map. fhir:Coding.userSelected.map a fhir:Map; fhir:target dt:CDCoding.codingRationale. fhir:Coding.userSelectedtrue a [ fhir:sourcefhir:target dt:CDCoding.codingRationaleO ]"
}
]
},
{
"id": "Extension.value[x]:valueCodeableConcept.text",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable",
"valueBoolean": true
}
],
"path": "Extension.valueCodeableConcept.text",
"short": "Plain text representation of the concept",
"definition": "A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.",
"comment": "Very often the text is the same as a displayName of one of the codings.",
"requirements": "The codes from the terminologies do not always capture the correct meaning with all the nuances of the human using them, or sometimes there is no appropriate code at all. In these cases, the text is used to capture the full meaning of the source.",
"min": "0",
"max": "1",
"base": {
"path": "CodeableConcept.text",
"min": "0",
"max": "1"
},
"type": [
{
"code": "string"
}
],
"condition": [
"ele-1"
],
"constraint": [
{
"key": "ele-1",
"severity": "error",
"human": "All FHIR elements must have a @value or children",
"expression": "hasValue() | (children().count() > id.count())",
"xpath": "@value|f:*|h:div"
}
],
"isSummary": true,
"mapping": [
{
"identity": "rim",
"map": "n/a"
},
{
"identity": "v2",
"map": "C*E.9. But note many systems use C*E.2 for this"
},
{
"identity": "rim",
"map": "./originalText[mediaType/code=/data"
},
{
"identity": "orim",
"map": "fhir:CodeableConcept.text rdfs:subPropertyOf dt:CD.originalText"
}
]
}
]
},
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Finnish PHR medicationcontext extension",
"definition": "Finnish PHR Medication Context Extension to express observation timing related to medication."
},
{
"id": "Extension.url",
"path": "Extension.url",
"fixedUri": "http://phr.kanta.fi/StructureDefinition/fiphr-medicationcontext"
},
{
"id": "Extension.value[x]:valueCodeableConcept",
"path": "Extension.valueCodeableConcept",
"sliceName": "valueCodeableConcept",
"type": [
{
"code": "CodeableConcept"
}
],
"binding": {
"strength": "required",
"valueSetUri": "http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext"
}
},
{
"id": "valueCodeableConcept:valueCodeableConcept.coding.system",
"path": "Extension.valueCodeableConcept.coding.system",
"min": "1",
"fixedUri": "http://phr.kanta.fi/fiphr-cs-medicationcontext"
},
{
"id": "valueCodeableConcept:valueCodeableConcept.coding.code",
"path": "Extension.valueCodeableConcept.coding.code",
"min": "1"
}
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
{
"resourceType": "Observation",
"meta": {
"profile": [
"http://phr.kanta.fi/StructureDefinition/fiphr-pef-stu3"
],
"security": [
{
"system": "http://hl7.org/fhir/v3/Confidentiality",
"code": "U",
"display": "unrestricted"
}
]
},
"language": "fi",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><div class=\"hapiHeaderText\"> Mittaustulos<b>Boldattuu </b></div><table class=\"hapiPropertyTable\"><tbody><tr><td>Identifier</td><td>Patient/7cf4b0bd-cfb3-493b-a6ee-b290f1a2a72e</td></tr></tbody></table></div>"
},
"identifier": [
{
"system": "urn:ietf:rfc:3986",
"value": "urn:uuid:187e0c12-8dd2-67e2-99b2-bf273a9549222"
}
],
"contained": [
{
"resourceType": "Device",
"id": "dev1",
"meta": {
"profile": [
"http://phr.kanta.fi/StructureDefinition/fiphr-device"
]
},
"identifier": [
{
"system": "urn:ietf:rfc:3986",
"value": "urn:uuid:187e0c12-8dd2-67e2-99b2-bf273a9549222"
}
],
"udi": {
"deviceIdentifier": "Device123",
"name": "Scalpel, single-use"
}
}
],
"status": "final",
"category": [
{
"coding": [
{
"system": "http://hl7.org/fhir/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "19935-6",
"display": "Maximum expiratory gas flow Respiratory system airway by Peak flow meter"
}
]
},
"subject": {
"reference": "Patient/7cf4b0bd-cfb3-493b-a6ee-b290f1a2a72e"
},
"effectiveDateTime": "2017-11-22",
"issued": "2017-11-22T16:52:00.943+03:00",
"performer": [
{
"reference": "Patient/7cf4b0bd-cfb3-493b-a6ee-b290f1a2a72e"
}
],
"valueQuantity": {
"value": 134,
"unit": "L/min",
"system": "http://unitsofmeasure.org",
"code": "L/min"
},
"interpretation": {
"coding": [
{
"system": "http://hl7.org/fhir/v2/0078",
"code": "<",
"display": "Off scale low"
}
]
},
"comment": "tässä on kommentteja",
"method": {
"coding": [
{
"system": "http://phr.kanta.fi/fiphr-cs-observationmethod",
"code": "1",
"display": "maxima"
}
],
"text": "tässä tässä mehhod"
},
"device": {
"reference": "#dev1"
},
"referenceRange": [
{
"low":
{
"value": 10,
"unit": "L/min",
"system": "http://hl7.org/fhir/v2/0078",
"code": "LU"
}
,
"high":
{
"value": 16,
"unit": "L/min",
"system": "http://hl7.org/fhir/v2/0078",
"code": "LU"
}
,
"appliesTo": [
{
"coding": [
{
"system": "http://hl7.org/fhir/referencerange-meaning",
"code": "normal",
"display": "Normal Range"
}
],
"text": "T�ss� teksti�"
}
],
"age": {
"low":
{
"value": 1,
"unit": "years",
"system": "http://unitsofmeasure.org",
"code": "21612-7"
}
,
"high":
{
"value": 1,
"unit": "years",
"system": "http://unitsofmeasure.org",
"code": "21612-7"
}
},
"text": "Sopiva ikä"
}
],
"extension": [
{
"url": "http://phr.kanta.fi/StructureDefinition/fiphr-medicationcontext",
"valueCodeableConcept": {
"coding":[
{
"system": "http://phr.kanta.fi/fiphr-cs-medicationcontext",
"code": "13",
"display": "Before medication"
}
],
"text": "mun teksti"
}
},
{
"url": "http://phr.kanta.fi/StructureDefinition/fiphr-boolean",
"valueBoolean": true
}
]
}

View File

@ -0,0 +1,82 @@
{
"resourceType": "ValueSet",
"id": "fiphr-vs-confidentiality",
"meta": {
"versionId": "1",
"lastUpdated": "2017-03-02T13:56:51.448+00:00",
"profile": [
"http://hl7.org/fhir/StructureDefinition/valueset-shareable-definition"
]
},
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">!-- Snipped for Brevity --&gt;</div>"
},
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/valueset-oid",
"valueUri": "urn:oid:2.16.840.1.113883.4.642.2.37"
}
],
"url": "http://phr.kanta.fi/ValueSet/fiphr-vs-confidentiality",
"version": "0.01",
"name": "Value Set Finnish PHR Confidentiality",
"status": "draft",
"experimental": true,
"date": "2016-11-23T06:00:00+00:00",
"publisher": "Kela",
"contact": [
{
"telecom": [
{
"system": "email",
"value": "kantakehitys@kanta.fi"
},
{
"system": "other",
"value": "http://www.kanta.fi/en/web/ammattilaisille/omakannan-omatietovarannon-maarittelyt"
}
]
}
],
"description": "Value set for confidentiality codes used in finnish PHR",
"immutable": false,
"extensible": false,
"compose": {
"include": [
{
"system": "http://hl7.org/fhir/v3/Confidentiality",
"concept": [
{
"code": "R",
"display": "Restricted",
"designation": [
{
"language": "fi",
"value": "Yksityinen"
},
{
"language": "en",
"value": "Restricted"
}
]
},
{
"code": "U",
"display": "Unrestricted",
"designation": [
{
"language": "en",
"value": "Unrestricted"
},
{
"language": "fi",
"value": "Suojaamaton"
}
]
}
]
}
]
}
}

View File

@ -0,0 +1,23 @@
{
"resourceType": "ValueSet",
"id": "fiphr-vs-medicationcontext",
"meta": {
"profile": [
"http://hl7.org/fhir/StructureDefinition/valueset-shareable-definition"
]
},
"url": "http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext",
"version": "0.01",
"name": "Value Set Finnish PHR Medication Context",
"status": "draft",
"date": "2016-11-23T06:00:00+00:00",
"publisher": "Kela",
"description": "Finnish PHR Medication Context value set.",
"compose": {
"include": [
{
"system": "http://phr.kanta.fi/fiphr-cs-medicationcontext"
}
]
}
}

View File

@ -0,0 +1,24 @@
{
"resourceType": "ValueSet",
"id": "fiphr-vs-observationmethod",
"meta": {
"profile": [
"http://hl7.org/fhir/StructureDefinition/valueset-shareable-definition"
]
},
"url": "http://phr.kanta.fi/ValueSet/fiphr-vs-observationmethod",
"version": "0.01",
"name": "Value Set Finnish PHR Observation Method",
"status": "draft",
"experimental": true,
"date": "2016-11-23T06:00:00+00:00",
"publisher": "Kela",
"description": "Finnish PHR Observation Method value set",
"compose": {
"include": [
{
"system": "http://phr.kanta.fi/fiphr-cs-observationmethod"
}
]
}
}

View File

@ -0,0 +1,287 @@
{
"resourceType": "ValueSet",
"id": "fiphr-vs-vitalsigns",
"url": "http://phr.kanta.fi/ValueSet/fiphr-vs-vitalsigns",
"version": "0.03",
"name": "Value Set Finnish PHR Vital Signs",
"status": "draft",
"date": "2017-11-27T13:50:00+02:00",
"publisher": "Kela",
"description": "This value set indicates the allowed vital sign result types in Finnish PRH. The concept Vitals signs panel (85353-1) is a grouping structure for a set of vital signs and includes related links (with type=has-member) to the Observations in this set (e.g. respiratory rate, heart rate, BP). The Blood pressure panel (85354-9) is used to group the component observations Systolic blood pressure (8480-6) and Diastolic blood pressure (8462-4).",
"copyright": "This content from LOINC? is copyright ? 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use",
"compose": {
"include": [
{
"system": "http://loinc.org",
"concept": [
{
"code": "85353-1",
"display": "Vital signs, weight, height, head circumference, oxygen saturation & BMI panel",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "översätts inte"
}
]
},
{
"code": "9279-1",
"display": "Respiratory rate",
"designation": [
{
"language": "fi",
"value": "Hengitystiheys"
},
{
"language": "sv",
"value": "Andningsfrekvens"
}
]
},
{
"code": "8867-4",
"display": "Heart rate",
"designation": [
{
"language": "fi",
"value": "Syke"
},
{
"language": "sv",
"value": "Puls"
}
]
},
{
"code": "41924-2",
"display": "Average heart rate 24h",
"designation": [
{
"language": "fi",
"value": "Keskisyke 24h"
},
{
"language": "sv",
"value": "Medelpuls 24h"
}
]
},
{
"code": "8883-1",
"display": "Minimum heart rate 24h",
"designation": [
{
"language": "fi",
"value": "Leposyke 24h"
},
{
"language": "sv",
"value": "Vilopuls 24h"
}
]
},
{
"code": "8873-2",
"display": "Maximum heart rate 24h",
"designation": [
{
"language": "fi",
"value": "Maksimisyke 24h"
},
{
"language": "sv",
"value": "Maxpuls 24h"
}
]
},
{
"code": "8310-5",
"display": "Body temperature",
"designation": [
{
"language": "fi",
"value": "Kehon lämpö"
},
{
"language": "sv",
"value": "Kroppstemperatur"
}
]
},
{
"code": "8331-1",
"display": "Oral temperature",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "ei käännetty"
}
]
},
{
"code": "8332-9",
"display": "Rectal temperature",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "ei käännetty"
}
]
},
{
"code": "76011-6",
"display": "Ear temperature",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "ei käännetty"
}
]
},
{
"code": "8328-7",
"display": "Axillary temperature",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "ei käännetty"
}
]
},
{
"code": "8302-2",
"display": "Body height",
"designation": [
{
"language": "fi",
"value": "Pituus"
},
{
"language": "sv",
"value": "Längd"
}
]
},
{
"code": "29463-7",
"display": "Body weight",
"designation": [
{
"language": "fi",
"value": "Paino"
},
{
"language": "sv",
"value": "Vikt"
}
]
},
{
"code": "85354-9",
"display": "Blood pressure panel with all children optional",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "översätts inte"
}
]
},
{
"code": "8480-6",
"display": "Systolic blood pressure",
"designation": [
{
"language": "fi",
"value": "Systolinen verenpaine"
},
{
"language": "sv",
"value": "Systoliskt blodtryck"
}
]
},
{
"code": "8462-4",
"display": "Diastolic blood pressure",
"designation": [
{
"language": "fi",
"value": "Diastolinen verenpaine"
},
{
"language": "sv",
"value": "Diastoliskt blodtryck"
}
]
},
{
"code": "77135-2",
"display": "Glucose [Moles/?volume] in Serum, Plasma or Blood",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "översätts inte"
}
]
},
{
"code": "19935-6",
"display": "Maximum expiratory gas flow Respiratory system airway by Peak flow meter",
"designation": [
{
"language": "fi",
"value": "ei käännetty"
},
{
"language": "sv",
"value": "översätts inte"
}
]
},
{
"code": "8280-0",
"display": "Waist Circumference at umbilicus by Tape measure",
"designation": [
{
"language": "fi",
"value": "Vyötärönympärys"
},
{
"language": "sv",
"value": "Midjemått"
}
]
}
]
}
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,9 @@ import ca.uhn.fhir.tinder.ValueSetGenerator;
import ca.uhn.fhir.tinder.VelocityHelper;
import ca.uhn.fhir.tinder.model.*;
import ca.uhn.fhir.tinder.model.SimpleSetter.Parameter;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -455,8 +458,8 @@ public abstract class BaseStructureParser {
scanForTypeNameConflicts(theNext, typeNames);
}
public void setExtensions(ArrayList<Extension> theExts) {
myExtensions = theExts;
public void setExtensions(ArrayList<Extension> theExtensions) {
myExtensions = theExtensions;
}
public void setVelocityProperties(String theVelocityProperties) {
@ -464,12 +467,7 @@ public abstract class BaseStructureParser {
}
private void write(BaseRootType theResource, File theFile, String thePackageBase) throws IOException, MojoFailureException {
FileOutputStream fos = new FileOutputStream(theFile, false);
OutputStreamWriter w = new OutputStreamWriter(fos, "UTF-8");
ourLog.debug("Writing file: {}", theFile.getAbsolutePath());
ArrayList<String> imports = new ArrayList<String>();
ArrayList<String> imports = new ArrayList<>();
for (String next : myImports) {
next = Resource.correctName(next);
if (next.contains(".")) {
@ -535,10 +533,36 @@ public abstract class BaseStructureParser {
}
InputStreamReader templateReader = new InputStreamReader(templateIs);
v.evaluate(ctx, w, "", templateReader);
ByteArrayOutputStream byteArrayWriter = new ByteArrayOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(byteArrayWriter, Charsets.UTF_8);
v.evaluate(ctx, outputStreamWriter, "", templateReader);
outputStreamWriter.flush();
w.close();
fos.close();
byte[] bytesToWrite = byteArrayWriter.toByteArray();
boolean actuallyWrite = false;
if (!theFile.exists()) {
actuallyWrite = true;
} else if (FileUtils.sizeOf(theFile) != bytesToWrite.length) {
actuallyWrite = true;
} else {
byte[] existingBytes = IOUtils.toByteArray(new FileInputStream(theFile));
if (!Arrays.equals(existingBytes, bytesToWrite)) {
actuallyWrite = true;
}
}
if (!actuallyWrite) {
ourLog.info("Skipping writing already up-to-date file: {}", theFile.getAbsolutePath());
return;
}
ourLog.debug("Writing file: {}", theFile.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(theFile, false)) {
fos.write(bytesToWrite);
fos.flush();
}
}
public void writeAll(File theOutputDirectory, File theResourceOutputDirectory, String thePackageBase) throws MojoFailureException {

47
pom.xml
View File

@ -408,6 +408,8 @@
<!-- Dependency Versions -->
<derby_version>10.14.1.0</derby_version>
<jaxb_api_version>2.3.0</jaxb_api_version>
<jaxb_core_version>2.3.0</jaxb_core_version>
<jersey_version>2.25.1</jersey_version>
<jetty_version>9.4.8.v20171121</jetty_version>
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
@ -580,11 +582,26 @@
<artifactId>javax.json-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb_api_version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>${jaxb_core_version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb_core_version}</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
@ -1063,9 +1080,9 @@
<plugin>
<groupId>de.juplo</groupId>
<artifactId>hibernate-maven-plugin</artifactId>
<version>2.0.0</version>
<version>2.1.1</version>
<configuration>
<export>false</export>
<execute>false</execute>
<skip>false</skip>
<scanDependencies>false</scanDependencies>
<scanTestClasses>false</scanTestClasses>
@ -1972,10 +1989,7 @@
</build>
</profile>
<profile>
<id>ERRORPRONE</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<id>ERRORPRONE_JDK8</id>
<build>
<plugins>
<plugin>
@ -1988,6 +2002,27 @@
</plugins>
</build>
</profile>
<profile>
<id>ERRORPRONE_JDK9</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerId>javac-with-errorprone</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -8,20 +8,9 @@
<body>
<release version="3.3.0" date="TBD">
<action type="add">
The version of a few dependencies have been bumped to the
latest versions (dependent HAPI modules listed in brackets):
<![CDATA[
<ul>
<li>Hibernate (JPA): 5.2.10.Final -&gt; 5.2.12.Final</li>
<li>Spring (JPA): 5.0.0 -&gt; 5.0.3</li>
<li>Thymeleaf (Web Tespage Overlay): 3.0.7.RELEASE -&gt; 3.0.9.RELEASE</li>
</ul>
]]>
</action>
<action type="add">
This release corrects an inefficiency in the JPA Server, but requires a schema
change in order to update. Prior to this version of HAPI FHIR, a CLOB column
containing the complete resource body was stored in two
containing the complete resource body was stored in two
tables: HFJ_RESOURCE and HFJ_RES_VER. Because the same content was stored in two
places, the database consumed more space than is needed to.
<![CDATA[<br/><br/>]]>
@ -32,6 +21,26 @@
set them to nullable if you want an easy means of rolling back). Naturally
you should back your database up prior to making this change.
</action>
<action type="fix">
The validation module has been refactored to use the R4 (currently maintained)
validator even for DSTU3 validation. This is done by using an automatic
converter which converts StructureDefinition/ValueSet/CodeSystem resources
which are used as inputs to the validator. This change should fix a number
of known issues with the validator, as they have been fixed in R4 but
not in DSTU3. This also makes our validator much more maintainable
since it is now one codebase.
</action>
<action type="add">
The version of a few dependencies have been bumped to the
latest versions (dependent HAPI modules listed in brackets):
<![CDATA[
<ul>
<li>Hibernate (JPA): 5.2.10.Final -&gt; 5.2.12.Final</li>
<li>Spring (JPA): 5.0.0 -&gt; 5.0.3</li>
<li>Thymeleaf (Web Tespage Overlay): 3.0.7.RELEASE -&gt; 3.0.9.RELEASE</li>
</ul>
]]>
</action>
<action type="fix">
Fix a crash in the JSON parser when parsing extensions on repeatable
elements (e.g. Patient.address.line) where there is an extension on the
@ -67,6 +76,28 @@
accommodate future needs which means that this should be the last
time we have to change it.
</action>
<action type="fix" issue="838">
The HAPI-FHIR-CLI now explicitly includes JAXB dependencies in its combined JAR
file. These were not neccesary prior to Java 9, but the JDK (mercifully) does
not include JAXB in the default classpath as of Java 9. This means that
it is possible to perform Schematron validation on Java 9. Thanks to
Mark Grimes for reporting and suggesting a fix!
</action>
<action type="add">
An experimental interceptor called VersionedApiConverterInterceptor has been added,
which automaticaly converts response payloads to a client-specified version
according to transforms built into FHIR.
</action>
<action type="fix" issue="822">
Searches which were embedded in a Bundle as a transaction or batch operation did
not respect any chained method parameters (e.g. MedicationRequest?medication.code=123).
Thanks to @manjusampath for reporting!
</action>
<action type="fix">
A few fixes went into the build which should now allow HAPI FHIR
to build correctly on JDK 9.0. Currently building is supported on
JDK 8.x and 9.x only.
</action>
</release>
<release version="3.2.0" date="2018-01-13">
<action type="add">