Add support for $snapshot generation and validation based on snapshots

This commit is contained in:
James Agnew 2019-07-14 16:15:56 -04:00
parent 120b93f204
commit 7831e8a0ed
53 changed files with 1388 additions and 4981 deletions

View File

@ -263,7 +263,13 @@ public class ValidatorExamples {
// TODO: implement
return null;
}
};
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
// TODO: implement
return null;
}
};
/*
* ValidationSupportChain strings multiple instances of IValidationSupport together. The

View File

@ -75,4 +75,16 @@ public class ValidateUtil {
}
}
public static void exactlyOneNotNullOrThrowInvalidRequestException(Object[] theObjects, String theMessage) {
int count = 0;
for (Object next : theObjects) {
if (next != null) {
count++;
}
}
if (count != 1) {
throw new InvalidRequestException(theMessage);
}
}
}

View File

@ -95,6 +95,11 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
return null;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return null;
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return Collections.emptyList();

View File

@ -92,6 +92,11 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
return false;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return null;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;

View File

@ -0,0 +1,32 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
public class FhirResourceDaoStructureDefinitionDstu2 extends FhirResourceDaoDstu2<StructureDefinition> implements IFhirResourceDaoStructureDefinition<StructureDefinition> {
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
// FIXME: implement
return null;
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao;
import org.hl7.fhir.instance.model.api.IBaseResource;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
public interface IFhirResourceDaoStructureDefinition<T extends IBaseResource> extends IFhirResourceDao<T> {
T generateSnapshot(T theInput, String theUrl, String theName);
}

View File

@ -0,0 +1,41 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.springframework.beans.factory.annotation.Autowired;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
public class FhirResourceDaoStructureDefinitionDstu3 extends FhirResourceDaoDstu3<StructureDefinition> implements IFhirResourceDaoStructureDefinition<StructureDefinition> {
@Autowired
private IValidationSupport myValidationSupport;
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
StructureDefinition output = myValidationSupport.generateSnapshot(theInput, theUrl, theName);
Validate.notNull(output);
return output;
}
}

View File

@ -128,7 +128,11 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
}
} else if ("StructureDefinition".equals(resourceName)) {
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return null;
// Don't allow the core FHIR definitions to be overwritten
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myDstu3Ctx.getElementDefinition(typeName) != null) {
return null;
}
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
@ -189,4 +193,9 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
return null;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return null;
}
}

View File

@ -0,0 +1,41 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.springframework.beans.factory.annotation.Autowired;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
public class FhirResourceDaoStructureDefinitionR4 extends FhirResourceDaoR4<StructureDefinition> implements IFhirResourceDaoStructureDefinition<StructureDefinition> {
@Autowired
private IValidationSupport myValidationSupport;
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
StructureDefinition output = myValidationSupport.generateSnapshot(theInput, theUrl, theName);
Validate.notNull(output);
return output;
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.StructureDefinition;
/*
* #%L
@ -23,5 +24,5 @@ import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
*/
public interface IJpaValidationSupportR4 extends IValidationSupport {
// nothing yet
}

View File

@ -1,16 +1,23 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -19,6 +26,7 @@ import org.springframework.context.ApplicationContextAware;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -121,7 +129,9 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
search = myValueSetDao.search(params);
}
} else if ("StructureDefinition".equals(resourceName)) {
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
// Don't allow the core FHIR definitions to be overwritten
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myR4Ctx.getElementDefinition(typeName) != null) {
return null;
}
SearchParameterMap params = new SearchParameterMap();
@ -183,4 +193,9 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
return null;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return null;
}
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.util;
package ca.uhn.fhir.jpa.provider;
/*
* #%L
@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.util;
* 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
* 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,
@ -20,10 +20,10 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
/**
* @deprecated Use {@link ca.uhn.fhir.util.StopWatch} instead
*/
@Deprecated
public class StopWatch extends ca.uhn.fhir.util.StopWatch {
// this just exists since existing code may depend on it
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
public class BaseJpaResourceProviderStructureDefinitionDstu2 extends JpaResourceProviderDstu2<StructureDefinition> {
// nothing yet
}

View File

@ -0,0 +1,81 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ValidateUtil;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
public class BaseJpaResourceProviderStructureDefinitionDstu3 extends JpaResourceProviderDstu3<StructureDefinition> {
/**
* <code>$snapshot</code> operation
*/
@Operation(name=JpaConstants.OPERATION_SNAPSHOT, idempotent = true)
public StructureDefinition snapshot(
@IdParam(optional = true) IdType theId,
@OperationParam(name = "definition") StructureDefinition theStructureDefinition,
@OperationParam(name = "url") StringType theUrl,
RequestDetails theRequestDetails) {
ValidateUtil.exactlyOneNotNullOrThrowInvalidRequestException(
new Object[]{ theId, theStructureDefinition, theUrl },
"Must supply either an ID or a StructureDefinition or a URL (but not more than one of these things)"
);
StructureDefinition sd;
if (theId == null && theStructureDefinition != null && theUrl == null) {
sd = theStructureDefinition;
} else if (theId != null && theStructureDefinition == null) {
sd = getDao().read(theId, theRequestDetails);
} else {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronousUpTo(2);
map.add(StructureDefinition.SP_URL, new UriParam(theUrl.getValue()));
IBundleProvider outcome = getDao().search(map, theRequestDetails);
if (outcome.size() == 0) {
throw new ResourceNotFoundException("No StructureDefiniton found with url = '" + theUrl.getValue() + "'");
}
sd = (StructureDefinition) outcome.getResources(0, 1).get(0);
}
return getDao().generateSnapshot(sd, null, null);
}
@Override
public IFhirResourceDaoStructureDefinition<StructureDefinition> getDao() {
return (IFhirResourceDaoStructureDefinition<StructureDefinition>) super.getDao();
}
}

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ValidateUtil;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
public class BaseJpaResourceProviderStructureDefinitionR4 extends JpaResourceProviderR4<StructureDefinition> {
/**
* <code>$snapshot</code> operation
*/
@Operation(name=JpaConstants.OPERATION_SNAPSHOT, idempotent = true)
public StructureDefinition snapshot(
@IdParam(optional = true) IdType theId,
@OperationParam(name = "definition") StructureDefinition theStructureDefinition,
@OperationParam(name = "url") StringType theUrl,
RequestDetails theRequestDetails) {
ValidateUtil.exactlyOneNotNullOrThrowInvalidRequestException(
new Object[]{ theId, theStructureDefinition, theUrl },
"Must supply either an ID or a StructureDefinition or a URL (but not more than one of these things)"
);
StructureDefinition sd;
if (theId == null && theStructureDefinition != null && theUrl == null) {
sd = theStructureDefinition;
} else if (theId != null && theStructureDefinition == null) {
sd = getDao().read(theId, theRequestDetails);
} else {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronousUpTo(2);
map.add(StructureDefinition.SP_URL, new UriParam(theUrl.getValue()));
IBundleProvider outcome = getDao().search(map, theRequestDetails);
if (outcome.size() == 0) {
throw new ResourceNotFoundException("No StructureDefiniton found with url = '" + theUrl.getValue() + "'");
}
sd = (StructureDefinition) outcome.getResources(0, 1).get(0);
}
return getDao().generateSnapshot(sd, null, null);
}
@Override
public IFhirResourceDaoStructureDefinition<StructureDefinition> getDao() {
return (IFhirResourceDaoStructureDefinition<StructureDefinition>) super.getDao();
}
}

View File

@ -297,5 +297,10 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
return new CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return null;
}
}

View File

@ -225,6 +225,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
return myTerminologySvc.supportsSystem(theSystem);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return null;
}
@CoverageIgnore
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {

View File

@ -142,4 +142,9 @@ public class JpaConstants {
* Operation name for the "$subsumes" operation
*/
public static final String OPERATION_SUBSUMES = "$subsumes";
/**
* Operation name for the "$snapshot" operation
*/
public static final String OPERATION_SNAPSHOT = "$snapshot";
}

View File

@ -20,31 +20,52 @@ package ca.uhn.fhir.jpa.validation;
* #L%
*/
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.hapi.validation.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class JpaValidationSupportChainDstu3 extends ValidationSupportChain {
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
@Autowired
@Qualifier("myJpaValidationSupportDstu3")
public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 myJpaValidationSupportDstu3;
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
@Autowired
private IHapiTerminologySvcDstu3 myTerminologyService;
@Autowired
private FhirContext myFhirContext;
public JpaValidationSupportChainDstu3() {
super();
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri);
}
return super.fetchResource(theContext, theClass, theUri);
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
StructureDefinition retVal = super.fetchStructureDefinition(theCtx, theUrl);
if (retVal != null && !retVal.hasSnapshot()) {
retVal = generateSnapshot(retVal, theUrl, null);
}
return retVal;
}
public void flush() {
myDefaultProfileValidationSupport.flush();
}
@ -54,12 +75,13 @@ public class JpaValidationSupportChainDstu3 extends ValidationSupportChain {
addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupportDstu3);
addValidationSupport(myTerminologyService);
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this));
}
@PreDestroy
public void preDestroy() {
flush();
}
}

View File

@ -23,8 +23,12 @@ package ca.uhn.fhir.jpa.validation;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.validation.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -33,7 +37,10 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
public class JpaValidationSupportChainR4 extends ValidationSupportChain {
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
@Autowired
private FhirContext myFhirContext;
@Autowired
@Qualifier("myJpaValidationSupportR4")
public ca.uhn.fhir.jpa.dao.r4.IJpaValidationSupportR4 myJpaValidationSupportR4;
@ -49,11 +56,31 @@ public class JpaValidationSupportChainR4 extends ValidationSupportChain {
myDefaultProfileValidationSupport.flush();
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri);
}
return super.fetchResource(theContext, theClass, theUri);
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
StructureDefinition retVal = super.fetchStructureDefinition(theCtx, theUrl);
if (retVal != null && !retVal.hasSnapshot()) {
retVal = generateSnapshot(retVal, theUrl, null);
}
return retVal;
}
@PostConstruct
public void postConstruct() {
addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupportR4);
addValidationSupport(myTerminologyService);
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this));
}
@PreDestroy

View File

@ -221,7 +221,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
@Autowired
@Qualifier("myStructureDefinitionDaoDstu3")
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
protected IFhirResourceDaoStructureDefinition<StructureDefinition> myStructureDefinitionDao;
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
protected IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
@SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoDstu3StructureDefinitionTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3StructureDefinitionTest.class);
@After
public final void after() {
}
@Test
public void testGenerateSnapshot() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/dstu3/profile-differential-patient-dstu3.json");
assertEquals(0, sd.getSnapshot().getElement().size());
StructureDefinition output = myStructureDefinitionDao.generateSnapshot(sd, "http://foo", "THE BEST PROFILE");
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(54, output.getSnapshot().getElement().size());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -426,6 +426,23 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
}
}
@Test
public void testValidateUsingDifferentialProfile() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/dstu3/profile-differential-patient-dstu3.json");
myStructureDefinitionDao.create(sd);
Patient p = new Patient();
p.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/MyPatient421");
p.setActive(true);
String raw = myFhirCtx.newJsonParser().encodeResourceToString(p);
MethodOutcome outcome = myPatientDao.validate(p, null, raw, EncodingEnum.JSON, null, null, mySrd);
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info("OO: {}", encoded);
assertThat(encoded, containsString("No issues detected"));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -255,7 +255,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
@Autowired
@Qualifier("myStructureDefinitionDaoR4")
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
protected IFhirResourceDaoStructureDefinition<StructureDefinition> myStructureDefinitionDao;
@Autowired
@Qualifier("myConsentDaoR4")
protected IFhirResourceDao<Consent> myConsentDao;

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoR4StructureDefinitionTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4StructureDefinitionTest.class);
@After
public final void after() {
}
@Test
public void testGenerateSnapshot() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-differential-patient-r4.json");
assertEquals(0, sd.getSnapshot().getElement().size());
StructureDefinition output = myStructureDefinitionDao.generateSnapshot(sd, "http://foo", "THE BEST PROFILE");
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
assertEquals(51, output.getSnapshot().getElement().size());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -446,6 +446,26 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
}
@Test
public void testValidateUsingDifferentialProfile() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-differential-patient-r4.json");
myStructureDefinitionDao.create(sd);
Patient p = new Patient();
p.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
p.getText().getDiv().setValue("<div>hello</div>");
p.getMeta().addProfile("http://example.com/fhir/StructureDefinition/patient-1a-extensions");
p.setActive(true);
String raw = myFhirCtx.newJsonParser().encodeResourceToString(p);
MethodOutcome outcome = myPatientDao.validate(p, null, raw, EncodingEnum.JSON, null, null, mySrd);
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info("OO: {}", encoded);
assertThat(encoded, containsString("No issues detected"));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class ResourceProviderDstu3StructureDefinitionTest extends BaseResourceProviderDstu3Test {
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testSnapshotWithResourceParameter() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/dstu3/profile-differential-patient-dstu3.json");
StructureDefinition response = ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "definition", sd)
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(54, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithId() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/dstu3/profile-differential-patient-dstu3.json");
IIdType id = ourClient.create().resource(sd).execute().getId().toUnqualifiedVersionless();
StructureDefinition response = ourClient
.operation()
.onInstance(id)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withNoParameters(Parameters.class)
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(54, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithUrl() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/dstu3/profile-differential-patient-dstu3.json");
IIdType id = ourClient.create().resource(sd).execute().getId().toUnqualifiedVersionless();
StructureDefinition response = ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://hl7.org/fhir/StructureDefinition/MyPatient421"))
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(54, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithUrlAndId() {
try {
ourClient
.operation()
.onInstance(new IdType("StructureDefinition/123"))
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://hl7.org/fhir/StructureDefinition/MyPatient421"))
.returnResourceType(StructureDefinition.class)
.execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Must supply either an ID or a StructureDefinition or a URL (but not more than one of these things)", e.getMessage());
}
}
@Test
public void testSnapshotWithInvalidUrl() {
try {
ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://hl7.org/fhir/StructureDefinition/FOO"))
.returnResourceType(StructureDefinition.class)
.execute();
} catch (ResourceNotFoundException e) {
assertEquals("HTTP 404 Not Found: No StructureDefiniton found with url = 'http://hl7.org/fhir/StructureDefinition/FOO'", e.getMessage());
}
}
}

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class ResourceProviderR4StructureDefinitionTest extends BaseResourceProviderR4Test {
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testSnapshotWithResourceParameter() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-differential-patient-r4.json");
StructureDefinition response = ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "definition", sd)
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(51, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithId() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-differential-patient-r4.json");
IIdType id = ourClient.create().resource(sd).execute().getId().toUnqualifiedVersionless();
StructureDefinition response = ourClient
.operation()
.onInstance(id)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withNoParameters(Parameters.class)
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(51, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithUrl() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-differential-patient-r4.json");
IIdType id = ourClient.create().resource(sd).execute().getId().toUnqualifiedVersionless();
StructureDefinition response = ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://example.com/fhir/StructureDefinition/patient-1a-extensions"))
.returnResourceType(StructureDefinition.class)
.execute();
assertEquals(51, response.getSnapshot().getElement().size());
}
@Test
public void testSnapshotWithUrlAndId() {
try {
ourClient
.operation()
.onInstance(new IdType("StructureDefinition/123"))
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://example.com/fhir/StructureDefinition/patient-1a-extensions"))
.returnResourceType(StructureDefinition.class)
.execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Must supply either an ID or a StructureDefinition or a URL (but not more than one of these things)", e.getMessage());
}
}
@Test
public void testSnapshotWithInvalidUrl() {
try {
ourClient
.operation()
.onType(StructureDefinition.class)
.named(JpaConstants.OPERATION_SNAPSHOT)
.withParameter(Parameters.class, "url", new StringType("http://hl7.org/fhir/StructureDefinition/FOO"))
.returnResourceType(StructureDefinition.class)
.execute();
} catch (ResourceNotFoundException e) {
assertEquals("HTTP 404 Not Found: No StructureDefiniton found with url = 'http://hl7.org/fhir/StructureDefinition/FOO'", e.getMessage());
}
}
}

View File

@ -0,0 +1,64 @@
{
"resourceType":"StructureDefinition",
"meta":{
"lastUpdated":"2017-08-26T18:27:24.959+01:00"
},
"url":"http://hl7.org/fhir/StructureDefinition/MyPatient421",
"name":"MyPatient",
"status":"draft",
"date":"2017-08-25T14:34:21.08+01:00",
"fhirVersion":"3.0.1",
"kind":"resource",
"abstract":false,
"type":"Patient",
"baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient",
"derivation":"constraint",
"differential":{
"element": [
{
"id":"Patient.id",
"path":"Patient.id",
"definition":"The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes."
},
{
"id":"Patient.extension",
"path":"Patient.extension",
"slicing":{
"discriminator": [
{
"type":"value",
"path":"url"
}
],
"rules":"open"
}
},
{
"id":"Patient.extension:us-core-race",
"path":"Patient.extension",
"sliceName":"us-core-race",
"type": [
{
"code":"Extension",
"profile":"http://hl7.org/fhir/StructureDefinition/us-core-race"
}
]
},
{
"id":"Patient.extension:us-core-religion",
"path":"Patient.extension",
"sliceName":"us-core-religion",
"short":"Optional Extensions Element",
"definition":"Optional Extension Element - found in all resources.",
"min":"0",
"max":"*",
"type": [
{
"code":"Extension",
"profile":"http://hl7.org/fhir/StructureDefinition/us-core-religion"
}
]
}
]
}
}

View File

@ -0,0 +1,80 @@
{
"resourceType":"StructureDefinition",
"url":"http://example.com/fhir/StructureDefinition/patient-1a-extensions",
"version":"0.1",
"name":"PatientWithExtensions",
"title":"Patient Profile 1 - 1a Extensions",
"status":"active",
"experimental":false,
"date":"2019",
"publisher":"Chris Grenz",
"contact": [
{
"telecom": [
{
"system":"email",
"value":"chris.grenz@thoughtworks.com"
}
]
}
],
"description":"Profile of Patient with extensions",
"copyright":"(c)2019 Chris Grenz into public domain",
"fhirVersion":"4.0.0",
"kind":"resource",
"abstract":false,
"type":"Patient",
"baseDefinition":"http://hl7.org/fhir/StructureDefinition/Patient",
"derivation":"constraint",
"differential":{
"element": [
{
"id":"Patient",
"path":"Patient"
},
{
"id":"Patient.extension",
"path":"Patient.extension"
},
{
"id":"Patient.extension:doNotCall",
"path":"Patient.extension",
"sliceName":"doNotCall",
"type": [
{
"code":"Extension",
"profile": [
"http://example.com/fhir/StructureDefinition/patient-donotcall"
]
}
]
},
{
"id":"Patient.extension:legalCase",
"path":"Patient.extension",
"sliceName":"legalCase",
"type": [
{
"code":"Extension",
"profile": [
"http://example.com/fhir/StructureDefinition/patient-legalcase"
]
}
]
},
{
"id":"Patient.extension:legalCase.value[x]:valueBoolean.extension:leadCounsel",
"path":"Patient.extension.valueBoolean.extension",
"sliceName":"leadCounsel",
"type": [
{
"code":"Extension",
"profile": [
"http://example.com/fhir/StructureDefinition/patient-legalcase-leadcounsel"
]
}
]
}
]
}
}

View File

@ -293,4 +293,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return null;
}
}

View File

@ -99,7 +99,16 @@ public interface IValidationSupport
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {
/**
* Generate a snapshot from the given differential profile.
*
* @param theInput
* @param theUrl
* @return Returns null if this module does not know how to handle this request
*/
StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {
public CodeValidationResult(ConceptDefinitionComponent theNext) {
super(theNext);

View File

@ -174,6 +174,11 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return null;
}
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);

View File

@ -14,104 +14,104 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.util.List;
public interface IValidationSupport
extends ca.uhn.fhir.context.support.IContextValidationSupport<ConceptSetComponent, ValueSetExpander.ValueSetExpansionOutcome, StructureDefinition, CodeSystem, ConceptDefinitionComponent, IssueSeverity> {
extends ca.uhn.fhir.context.support.IContextValidationSupport<ConceptSetComponent, ValueSetExpander.ValueSetExpansionOutcome, StructureDefinition, CodeSystem, ConceptDefinitionComponent, IssueSeverity> {
/**
* Expands the given portion of a ValueSet
*
* @param theInclude
* The portion to include
* @return The expansion
*/
@Override
ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude);
/**
* Expands the given portion of a ValueSet
*
* @param theInclude The portion to include
* @return The expansion
*/
@Override
ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude);
/**
* Load and return all possible structure definitions
*/
@Override
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Load and return all possible structure definitions
*/
@Override
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by Uri
*
* @param uri
* Canonical Uri of the code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
CodeSystem fetchCodeSystem(FhirContext theContext, String uri);
/**
* Fetch a code system by Uri
*
* @param uri Canonical Uri of the code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
CodeSystem fetchCodeSystem(FhirContext theContext, String uri);
/**
* Fetch a valueset by Uri
*
* @param uri
* Canonical Uri of the ValueSet
* @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
ValueSet fetchValueSet(FhirContext theContext, String uri);
/**
* Loads a resource needed by the validation (a StructureDefinition, or a
* ValueSet)
*
* @param theContext
* The HAPI FHIR Context object current in use by the validator
* @param theClass
* The type of the resource to load
* @param theUri
* The resource URI
* @return Returns the resource, or <code>null</code> if no resource with the
* given URI can be found
*/
@Override
<T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
/**
* Loads a resource needed by the validation (a StructureDefinition, or a
* ValueSet)
*
* @param theContext The HAPI FHIR Context object current in use by the validator
* @param theClass The type of the resource to load
* @param theUri The resource URI
* @return Returns the resource, or <code>null</code> if no resource with the
* given URI can be found
*/
@Override
<T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
@Override
StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl);
@Override
StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl);
/**
* Returns <code>true</code> if codes in the given code system can be expanded
* or validated
*
* @param theSystem
* The URI for the code system, e.g. <code>"http://loinc.org"</code>
* @return Returns <code>true</code> if codes in the given code system can be
* validated
*/
@Override
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
* Returns <code>true</code> if codes in the given code system can be expanded
* or validated
*
* @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code>
* @return Returns <code>true</code> if codes in the given code system can be
* validated
*/
@Override
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
* binding fields (e.g. <code>Observation.code</code> in the default profile.
*
* @param theCodeSystem
* The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode
* The code, e.g. "<code>1234-5</code>"
* @param theDisplay
* The display name, if it should also be validated
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
/**
* Generate a snapshot from the given differential profile.
*
* @param theInput
* @param theUrl
* @param theProfileName
* @return Returns null if this module does not know how to handle this request
*/
StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
* binding fields (e.g. <code>Observation.code</code> in the default profile.
*
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
public CodeValidationResult(ConceptDefinitionComponent theNext) {
super(theNext);
}
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {
public CodeValidationResult(IssueSeverity theSeverity, String theMessage) {
super(theSeverity, theMessage);
}
public CodeValidationResult(ConceptDefinitionComponent theNext) {
super(theNext);
}
public CodeValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
super(severity, message, definition);
}
public CodeValidationResult(IssueSeverity theSeverity, String theMessage) {
super(theSeverity, theMessage);
}
}
public CodeValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
super(severity, message, definition);
}
}
}

View File

@ -69,4 +69,9 @@ public class CachingValidationSupport implements IValidationSupport {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return myWrap.generateSnapshot(theInput, theUrl, theName);
}
}

View File

@ -142,7 +142,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return new ArrayList<StructureDefinition>(myStructureDefinitions.values());
return new ArrayList<>(myStructureDefinitions.values());
}
@Override
@ -185,4 +185,9 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
return null;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return null;
}
}

View File

@ -0,0 +1,139 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Simple validation support module that handles profile snapshot generation. This is
* separate from other funcrtions since it needs a link to a validation support
* module itself, and it is useful to be able to pass a chain in.
*/
public class SnapshotGeneratingValidationSupport implements IValidationSupport {
private final FhirContext myCtx;
private final IValidationSupport myValidationSupport;
public SnapshotGeneratingValidationSupport(FhirContext theCtx, IValidationSupport theValidationSupport) {
Validate.notNull(theCtx);
Validate.notNull(theValidationSupport);
myCtx = theCtx;
myValidationSupport = theValidationSupport;
}
@Override
public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) {
return null;
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
return null;
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return null;
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return null;
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return false;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
IWorkerContext context = new HapiWorkerContext(myCtx, myValidationSupport);
ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorker();
ArrayList<ValidationMessage> messages = new ArrayList<>();
StructureDefinition base = myValidationSupport.fetchStructureDefinition(myCtx, theInput.getBaseDefinition());
if (base == null) {
throw new PreconditionFailedException("Unknown base definition: " + theInput.getBaseDefinition());
}
new ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, theInput, theUrl, theProfileName);
return theInput;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;
}
private class MyProfileKnowledgeWorker implements ProfileUtilities.ProfileKnowledgeProvider {
@Override
public boolean isDatatype(String typeSimple) {
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(typeSimple);
Validate.notNull(typeSimple);
return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition);
}
@Override
public boolean isResource(String typeSimple) {
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(typeSimple);
Validate.notNull(typeSimple);
return def instanceof RuntimeResourceDefinition;
}
@Override
public boolean hasLinkFor(String typeSimple) {
return false;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition def, ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException {
return null;
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
return null;
}
@Override
public boolean prependLinks() {
return false;
}
}
}

View File

@ -81,11 +81,14 @@ public class ValidationSupportChain implements IValidationSupport {
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
ArrayList<StructureDefinition> retVal = new ArrayList<StructureDefinition>();
Set<String> urls = new HashSet<String>();
Set<String> urls = new HashSet<>();
for (IValidationSupport nextSupport : myChain) {
for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) {
if (isBlank(next.getUrl()) || urls.add(next.getUrl())) {
retVal.add(next);
List<StructureDefinition> list = nextSupport.fetchAllStructureDefinitions(theContext);
if (list != null) {
for (StructureDefinition next : list) {
if (isBlank(next.getUrl()) || urls.add(next.getUrl())) {
retVal.add(next);
}
}
}
}
@ -167,5 +170,16 @@ public class ValidationSupportChain implements IValidationSupport {
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
StructureDefinition outcome = null;
for (org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport next : myChain) {
outcome = next.generateSnapshot(theInput, theUrl, theProfileName);
if (outcome != null) {
break;
}
}
return outcome;
}
}

View File

@ -1,446 +0,0 @@
package org.hl7.fhir.dstu3.validation;
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.text.MessageFormat;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public class BaseValidator {
protected Source source;
/**
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean fail(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.FATAL));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
if (!thePass) {
String path = toPath(pathParts);
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
errors.add(new ValidationMessage(source, type, -1, -1, path, formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean fail(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
}
return thePass;
}
private String formatMessage(String theMessage, Object... theMessageArguments) {
String message;
if (theMessageArguments != null && theMessageArguments.length > 0) {
message = MessageFormat.format(theMessage, theMessageArguments);
} else {
message = theMessage;
}
return message;
}
protected boolean grammarWord(String w) {
return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of");
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.ERROR));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
if (!thePass) {
String path = toPath(pathParts);
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.ERROR));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
}
return thePass;
}
static public boolean rule(List<ValidationMessage> errors, Source source, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.ERROR));
}
return thePass;
}
protected String splitByCamelCase(String s) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i-1))))
b.append(' ');
b.append(c);
}
return b.toString();
}
protected String stripPunctuation(String s, boolean numbers) {
StringBuilder b = new StringBuilder();
for (char c : s.toCharArray()) {
int t = Character.getType(c);
if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ')
b.append(c);
}
return b.toString();
}
private String toPath(List<String> pathParts) {
if (pathParts == null || pathParts.isEmpty()) {
return "";
}
return "//" + StringUtils.join(pathParts, '/');
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass) {
msg = formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.WARNING));
}
return thePass;
}
protected boolean warningOrError(boolean isError, List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass) {
msg = formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, msg, isError ? IssueSeverity.ERROR : IssueSeverity.WARNING));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.WARNING));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.WARNING));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
if (!thePass) {
msg = formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
}
return thePass;
}
//---------
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass) {
msg = formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean suppressedwarning(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
if (!thePass) {
msg = formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.INFORMATION));
}
return thePass;
}
}

View File

@ -1,76 +0,0 @@
package org.hl7.fhir.dstu3.validation;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.exceptions.DefinitionException;
/**
* This interface is used to provide extension location services for the validator
*
* when it encounters an extension, it asks this server to locate it, or tell it
* whether to ignore the extension, or mark it as invalid
*
* @author Grahame
*
*/
public interface ExtensionLocatorService {
public enum Status {
Located, NotAllowed, Unknown
}
public class ExtensionLocationResponse {
private Status status;
private StructureDefinition definition;
private String message;
private String url;
public ExtensionLocationResponse(String url, Status status, StructureDefinition definition, String message) {
super();
this.url = url;
this.status = status;
this.definition = definition;
this.message = message;
}
public Status getStatus() {
return status;
}
public StructureDefinition getDefinition() {
return definition;
}
public String getMessage() {
return message;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
/**
* This routine is used when walking into a complex extension.
* the non-tail part of the relative URL matches the end of the
* exiting URL
* @param url - the relative URL
* @return
* @throws DefinitionException
* @
*/
public ExtensionLocationResponse clone(String url) throws DefinitionException {
if (!this.url.endsWith(url.substring(0, url.lastIndexOf("."))))
throw new DefinitionException("the relative URL "+url+" cannot be used in the context "+this.url);
return new ExtensionLocationResponse(this.url+"."+url.substring(url.lastIndexOf(".")+1), status, definition, message);
}
}
public ExtensionLocationResponse locateExtension(String uri);
}

View File

@ -1,42 +0,0 @@
package org.hl7.fhir.dstu3.validation;
import java.awt.EventQueue;
import java.io.IOException;
public class GraphicalValidator {
public ValidatorFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
GraphicalValidator window = new GraphicalValidator();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
* @throws IOException
*/
public GraphicalValidator() throws IOException {
initialize();
}
/**
* Initialize the contents of the frame.
* @throws IOException
*/
private void initialize() throws IOException {
frame = new ValidatorFrame();
}
}

View File

@ -1,72 +0,0 @@
package org.hl7.fhir.dstu3.validation;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class ProfileValidator extends BaseValidator {
IWorkerContext context;
public void setContext(IWorkerContext context) {
this.context = context;
}
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
return super.rule(errors, type, path, b, msg, "<a href=\""+(rn.toLowerCase())+".html\">"+rn+"</a>: "+Utilities.escapeXml(msg));
}
public List<ValidationMessage> validate(StructureDefinition profile, boolean forBuild) {
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
// first check: extensions must exist
for (ElementDefinition ec : profile.getDifferential().getElement())
checkExtensions(profile, errors, "differential", ec);
rule(errors, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "missing Snapshot at "+profile.getName()+"."+profile.getName());
for (ElementDefinition ec : profile.getSnapshot().getElement())
checkExtensions(profile, errors, "snapshot", ec);
if (rule(errors, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "A snapshot is required")) {
for (ElementDefinition ed : profile.getSnapshot().getElement()) {
checkExtensions(profile, errors, "snapshot", ed);
for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
if (forBuild) {
if (!inExemptList(inv.getKey())) {
if (rule(errors, IssueType.BUSINESSRULE, profile.getId()+"::"+ed.getPath()+"::"+inv.getKey(), inv.hasExpression(), "The invariant has no FHIR Path expression ("+inv.getXpath()+")")) {
try {
new FHIRPathEngine(context).check(null, profile.getType(), ed.getPath(), inv.getExpression()); // , inv.hasXpath() && inv.getXpath().startsWith("@value")
} catch (Exception e) {
// rule(errors, IssueType.STRUCTURE, profile.getId()+"::"+ed.getPath()+"::"+inv.getId(), exprExt != null, e.getMessage());
}
}
}
}
}
}
}
return errors;
}
// these are special cases
private boolean inExemptList(String key) {
return key.startsWith("txt-");
}
private void checkExtensions(StructureDefinition profile, List<ValidationMessage> errors, String kind, ElementDefinition ec) {
if (!ec.getType().isEmpty() && "Extension".equals(ec.getType().get(0).getCode()) && ec.getType().get(0).hasProfile()) {
String url = ec.getType().get(0).getProfile();
StructureDefinition defn = context.fetchResource(StructureDefinition.class, url);
rule(errors, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")");
}
}
}

View File

@ -1,393 +0,0 @@
package org.hl7.fhir.dstu3.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.InstantType;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.TimeType;
import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
/**
* Validates that an instance of {@link QuestionnaireResponse} is valid against the {@link Questionnaire} that it claims
* to conform to.
*
* @author James Agnew
*/
public class QuestionnaireResponseValidator extends BaseValidator {
/*
* ***************************************************************** Note to anyone working on this class -
*
* This class has unit tests which run within the HAPI project build. Please sync any changes here to HAPI and ensure
* that unit tests are run. ****************************************************************
*/
private IWorkerContext myWorkerCtx;
public QuestionnaireResponseValidator(IWorkerContext theWorkerCtx) {
this.myWorkerCtx = theWorkerCtx;
}
private Set<Class<? extends Type>> allowedTypes(Class<? extends Type> theClass0) {
return allowedTypes(theClass0, null);
}
private Set<Class<? extends Type>> allowedTypes(Class<? extends Type> theClass0, Class<? extends Type> theClass1) {
HashSet<Class<? extends Type>> retVal = new HashSet<Class<? extends Type>>();
retVal.add(theClass0);
if (theClass1 != null) {
retVal.add(theClass1);
}
return Collections.unmodifiableSet(retVal);
}
private List<QuestionnaireResponseItemComponent> findResponsesByLinkId(List<QuestionnaireResponseItemComponent> theItem, String theLinkId) {
Validate.notBlank(theLinkId, "theLinkId must not be blank");
ArrayList<QuestionnaireResponseItemComponent> retVal = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>();
for (QuestionnaireResponseItemComponent next : theItem) {
if (theLinkId.equals(next.getLinkId())) {
retVal.add(next);
}
}
return retVal;
}
public void validate(List<ValidationMessage> theErrors, QuestionnaireResponse theAnswers) {
LinkedList<String> pathStack = new LinkedList<String>();
pathStack.add("QuestionnaireResponse");
pathStack.add(QuestionnaireResponse.SP_QUESTIONNAIRE);
if (!fail(theErrors, IssueType.INVALID, pathStack, theAnswers.hasQuestionnaire(), "QuestionnaireResponse does not specity which questionnaire it is providing answers to")) {
return;
}
Reference questionnaireRef = theAnswers.getQuestionnaire();
Questionnaire questionnaire = getQuestionnaire(theAnswers, questionnaireRef);
if (!fail(theErrors, IssueType.INVALID, pathStack, questionnaire != null, "Questionnaire {0} is not found in the WorkerContext", theAnswers.getQuestionnaire().getReference())) {
return;
}
QuestionnaireResponseStatus status = theAnswers.getStatus();
boolean validateRequired = false;
if (status == QuestionnaireResponseStatus.COMPLETED || status == QuestionnaireResponseStatus.AMENDED) {
validateRequired = true;
}
pathStack.removeLast();
// pathStack.add("group(0)");
validateItems(theErrors, questionnaire.getItem(), theAnswers.getItem(), pathStack, theAnswers, validateRequired);
}
private Questionnaire getQuestionnaire(QuestionnaireResponse theAnswers, Reference theQuestionnaireRef) {
Questionnaire retVal;
String value = theQuestionnaireRef.getReferenceElement().getValue();
if (theQuestionnaireRef.getReferenceElement().isLocal()) {
retVal = (Questionnaire) theQuestionnaireRef.getResource();
if (retVal == null) {
for (Resource next : theAnswers.getContained()) {
if (value.equals(next.getId())) {
retVal = (Questionnaire) next;
}
}
}
} else {
retVal = myWorkerCtx.fetchResource(Questionnaire.class, value);
}
return retVal;
}
private ValueSet getValueSet(QuestionnaireResponse theResponse, Reference theQuestionnaireRef) {
ValueSet retVal;
if (theQuestionnaireRef.getReferenceElement().isLocal()) {
retVal = (ValueSet) theQuestionnaireRef.getResource();
if (retVal == null) {
for (Resource next : theResponse.getContained()) {
if (theQuestionnaireRef.getReferenceElement().getValue().equals(next.getId())) {
retVal = (ValueSet) next;
}
}
}
} else {
retVal = myWorkerCtx.fetchResource(ValueSet.class, theQuestionnaireRef.getReferenceElement().getValue());
}
return retVal;
}
private void validateGroup(List<ValidationMessage> theErrors, QuestionnaireItemComponent theQuestGroup, QuestionnaireResponseItemComponent theRespGroup, LinkedList<String> thePathStack, QuestionnaireResponse theResponse, boolean theValidateRequired) {
validateItems(theErrors, theQuestGroup.getItem(), theRespGroup.getItem(), thePathStack, theResponse, theValidateRequired);
}
private void validateQuestion(List<ValidationMessage> theErrors, QuestionnaireItemComponent theQuestion, QuestionnaireResponseItemComponent theRespGroup, LinkedList<String> thePathStack, QuestionnaireResponse theResponse, boolean theValidateRequired) {
String linkId = theQuestion.getLinkId();
if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), "Questionnaire is invalid, question found with no link ID")) {
return;
}
QuestionnaireItemType type = theQuestion.getType();
if (type == null) {
rule(theErrors, IssueType.INVALID, thePathStack, false, "Questionnaire is invalid, no type specified for question with link ID[{0}]", linkId);
return;
}
// List<QuestionnaireResponseItemComponent> responses;
// if (theRespGroup == null) {
// responses = findResponsesByLinkId(theResponse.getItem(), linkId);
// } else {
// responses = findResponsesByLinkId(theRespGroup.getItem(), linkId);
// }
List<QuestionnaireResponseItemAnswerComponent> responses = theRespGroup.getAnswer();
if (responses.size() > 1) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRepeats(), "Multiple answers found with linkId[{0}]", linkId);
}
if (responses.size() == 0) {
if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer item for required item with linkId[{0}]", linkId);
} else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer item for required item with linkId[{0}]", linkId);
}
return;
}
// QuestionnaireResponseItemComponent responseItem = responses.get(0);
try {
// thePathStack.add("item(" + responses.indexOf(responseItem) + ")");
validateQuestionAnswers(theErrors, theQuestion, thePathStack, type, theRespGroup, theResponse, theValidateRequired);
} finally {
// thePathStack.removeLast();
}
}
private void validateItems(List<ValidationMessage> theErrors, List<QuestionnaireItemComponent> theQuestionnaireItems, List<QuestionnaireResponseItemComponent> theResponseItems, LinkedList<String> thePathStack, QuestionnaireResponse theResponse, boolean theValidateRequired) {
Set<String> allowedItems = new HashSet<String>();
for (QuestionnaireItemComponent nextQuestionnaireItem : theQuestionnaireItems) {
if (nextQuestionnaireItem.getType()== QuestionnaireItemType.NULL || nextQuestionnaireItem.getType() == null) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Questionnaire definition contains item with no type");
String linkId = nextQuestionnaireItem.getLinkId();
if (isNotBlank(linkId)) {
// Just so that we don't also get a warning about the answer being present
allowedItems.add(linkId);
}
continue;
}
if (!QuestionnaireItemType.DISPLAY.equals(nextQuestionnaireItem.getType())) {
String itemType = QuestionnaireItemType.GROUP.equals(nextQuestionnaireItem.getType()) ? "group" : "question";
String linkId = nextQuestionnaireItem.getLinkId();
if (isBlank(linkId)) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Questionnaire definition contains {0} with no linkId", itemType);
continue;
}
allowedItems.add(linkId);
List<QuestionnaireResponseItemComponent> responseItems = findResponsesByLinkId(theResponseItems, linkId);
if (responseItems.isEmpty()) {
if (nextQuestionnaireItem.getRequired()) {
if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required {0} with linkId[{1}]", itemType, linkId);
} else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required {0} with linkId[{1}]", itemType, linkId);
}
}
continue;
}
if (responseItems.size() > 1) {
if (nextQuestionnaireItem.getRepeats() == false) {
int index = theResponseItems.indexOf(responseItems.get(1));
thePathStack.add("item(" + index + ")");
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Multiple repetitions of {0} with linkId[{1}] found at this position, but this item cannot repeat", itemType, linkId);
thePathStack.removeLast();
}
}
for (QuestionnaireResponseItemComponent nextResponseItem : responseItems) {
int index = theResponseItems.indexOf(nextResponseItem);
thePathStack.add("item(" + index + ")");
if (nextQuestionnaireItem.getType() == QuestionnaireItemType.GROUP) {
validateGroup(theErrors, nextQuestionnaireItem, nextResponseItem, thePathStack, theResponse, theValidateRequired);
} else {
validateQuestion(theErrors, nextQuestionnaireItem, nextResponseItem, thePathStack, theResponse, theValidateRequired);
}
thePathStack.removeLast();
}
}
}
// Make sure there are no items in response that aren't in the questionnaire
int idx = -1;
for (QuestionnaireResponseItemComponent next : theResponseItems) {
idx++;
if (!allowedItems.contains(next.getLinkId())) {
thePathStack.add("item(" + idx + ")");
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Item with linkId[{0}] found at this position, but this item does not exist at this position in Questionnaire", next.getLinkId());
thePathStack.removeLast();
}
}
}
private void validateQuestionAnswers(List<ValidationMessage> theErrors, QuestionnaireItemComponent theQuestion, LinkedList<String> thePathStack, QuestionnaireItemType type, QuestionnaireResponseItemComponent responseQuestion, QuestionnaireResponse theResponse, boolean theValidateRequired) {
String linkId = theQuestion.getLinkId();
Set<Class<? extends Type>> allowedAnswerTypes = determineAllowedAnswerTypes(type);
if (allowedAnswerTypes.isEmpty()) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, responseQuestion.isEmpty(), "Question with linkId[{0}] has no answer type but an answer was provided", linkId);
} else {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(responseQuestion.getAnswer().size() > 1 && !theQuestion.getRepeats()), "Multiple answers to non repeating question with linkId[{0}]", linkId);
if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && responseQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
} else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && responseQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
}
}
int answerIdx = -1;
for (QuestionnaireResponseItemAnswerComponent nextAnswer : responseQuestion.getAnswer()) {
answerIdx++;
try {
thePathStack.add("answer(" + answerIdx + ")");
Type nextValue = nextAnswer.getValue();
if (!allowedAnswerTypes.contains(nextValue.getClass())) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] found of type [{1}] but this is invalid for question of type [{2}]", linkId, nextValue.getClass().getSimpleName(), type.toCode());
continue;
}
// Validate choice answers
if (type == QuestionnaireItemType.CHOICE || type == QuestionnaireItemType.OPENCHOICE) {
if (nextAnswer.getValue() instanceof StringType) {
// n.b. we can only be here if it's an open-choice
String value = ((StringType)nextAnswer.getValue()).getValue();
if (isBlank(value)) {
if (Boolean.TRUE.equals(theQuestion.getRequiredElement().getValue())) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] has no value but this item is required", linkId);
}
}
} else {
Coding coding = (Coding) nextAnswer.getValue();
if (isBlank(coding.getCode()) && isBlank(coding.getDisplay()) && isBlank(coding.getSystem())) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is of type coding, but none of code, system, and display are populated", linkId);
continue;
} else if (isBlank(coding.getCode()) && isBlank(coding.getSystem())) {
if (type != QuestionnaireItemType.OPENCHOICE) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is of type only has a display populated (no code or system) but question does not allow {1}", linkId, QuestionnaireItemType.OPENCHOICE.name());
continue;
}
} else if (isBlank(coding.getCode()) || isBlank(coding.getSystem())) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] has a coding, but this coding does not contain a code and system (both must be present, or neither as the question allows {1})", linkId, QuestionnaireItemType.OPENCHOICE.name());
continue;
}
String optionsRef = theQuestion.getOptions().getReference();
if (isNotBlank(optionsRef)) {
ValueSet valueSet = getValueSet(theResponse, theQuestion.getOptions());
if (valueSet == null) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Question with linkId[{0}] has options ValueSet[{1}] but this ValueSet can not be found", linkId, optionsRef);
continue;
}
boolean found = false;
for (ValueSetExpansionContainsComponent next : valueSet.getExpansion().getContains()) {
if (coding.getCode().equals(next.getCode()) && coding.getSystem().equals(next.getSystem())) {
found = true;
break;
}
}
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, found, "Question with linkId[{0}] has answer with system[{1}] and code[{2}] but this is not a valid answer for ValueSet[{3}]", linkId, coding.getSystem(), coding.getCode(), optionsRef);
}
}
}
validateItems(theErrors, theQuestion.getItem(), nextAnswer.getItem(), thePathStack, theResponse, theValidateRequired);
} finally {
thePathStack.removeLast();
}
} // for answers
}
private Set<Class<? extends Type>> determineAllowedAnswerTypes(QuestionnaireItemType type) {
Set<Class<? extends Type>> allowedAnswerTypes;
switch (type) {
case ATTACHMENT:
allowedAnswerTypes = allowedTypes(Attachment.class);
break;
case BOOLEAN:
allowedAnswerTypes = allowedTypes(BooleanType.class);
break;
case CHOICE:
allowedAnswerTypes = allowedTypes(Coding.class);
break;
case DATE:
allowedAnswerTypes = allowedTypes(DateType.class);
break;
case DATETIME:
allowedAnswerTypes = allowedTypes(DateTimeType.class);
break;
case DECIMAL:
allowedAnswerTypes = allowedTypes(DecimalType.class);
break;
case INTEGER:
allowedAnswerTypes = allowedTypes(IntegerType.class);
break;
case OPENCHOICE:
allowedAnswerTypes = allowedTypes(Coding.class, StringType.class);
break;
case QUANTITY:
allowedAnswerTypes = allowedTypes(Quantity.class);
break;
case REFERENCE:
allowedAnswerTypes = allowedTypes(Reference.class);
break;
case STRING:
allowedAnswerTypes = allowedTypes(StringType.class);
break;
case TEXT:
allowedAnswerTypes = allowedTypes(StringType.class);
break;
case TIME:
allowedAnswerTypes = allowedTypes(TimeType.class);
break;
case URL:
allowedAnswerTypes = allowedTypes(UriType.class);
break;
case NULL:
default:
allowedAnswerTypes = Collections.emptySet();
}
return allowedAnswerTypes;
}
}

View File

@ -1,66 +0,0 @@
package org.hl7.fhir.dstu3.validation;
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.List;
import 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.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class ValidationErrorHandler implements ErrorHandler {
private List<ValidationMessage> outputs;
private String path;
public ValidationErrorHandler(List<ValidationMessage> outputs, String path) {
this.outputs = outputs;
this.path = path;
}
@Override
public void error(SAXParseException arg0) throws SAXException {
outputs.add(new ValidationMessage(Source.Schema, IssueType.INVALID, arg0.getLineNumber(), arg0.getColumnNumber(), path, arg0.getMessage(), IssueSeverity.ERROR));
}
@Override
public void fatalError(SAXParseException arg0) throws SAXException {
outputs.add(new ValidationMessage(Source.Schema, IssueType.INVALID, arg0.getLineNumber(), arg0.getColumnNumber(), path, arg0.getMessage(), IssueSeverity.FATAL));
}
@Override
public void warning(SAXParseException arg0) throws SAXException {
outputs.add(new ValidationMessage(Source.Schema, IssueType.INVALID, arg0.getLineNumber(), arg0.getColumnNumber(), path, arg0.getMessage(), IssueSeverity.WARNING));
}
}

View File

@ -1,11 +0,0 @@
package org.hl7.fhir.dstu3.validation;
import java.io.IOException;
public class ValidatorFrame extends javax.swing.JFrame {
public ValidatorFrame() throws IOException {
}
}

View File

@ -66,6 +66,11 @@ public class CachingValidationSupport implements IValidationSupport {
return myWrap.isCodeSystemSupported(theContext, theSystem);
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return myWrap.generateSnapshot(theInput, theUrl, theProfileName);
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay);

View File

@ -182,7 +182,12 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
return false;
}
@Override
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
return null;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;
}

View File

@ -0,0 +1,140 @@
package org.hl7.fhir.r4.hapi.validation;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Simple validation support module that handles profile snapshot generation. This is
* separate from other funcrtions since it needs a link to a validation support
* module itself, and it is useful to be able to pass a chain in.
*/
public class SnapshotGeneratingValidationSupport implements IValidationSupport {
private final FhirContext myCtx;
private final IValidationSupport myValidationSupport;
public SnapshotGeneratingValidationSupport(FhirContext theCtx, IValidationSupport theValidationSupport) {
Validate.notNull(theCtx);
Validate.notNull(theValidationSupport);
myCtx = theCtx;
myValidationSupport = theValidationSupport;
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) {
return null;
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
return null;
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return null;
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return null;
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return false;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
IWorkerContext context = new HapiWorkerContext(myCtx, myValidationSupport);
ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new MyProfileKnowledgeWorker();
ArrayList<ValidationMessage> messages = new ArrayList<>();
StructureDefinition base = myValidationSupport.fetchStructureDefinition(myCtx, theInput.getBaseDefinition());
if (base == null) {
throw new PreconditionFailedException("Unknown base definition: " + theInput.getBaseDefinition());
}
new ProfileUtilities(context, messages, profileKnowledgeProvider).generateSnapshot(base, theInput, theUrl, theProfileName);
return theInput;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;
}
private class MyProfileKnowledgeWorker implements ProfileUtilities.ProfileKnowledgeProvider {
@Override
public boolean isDatatype(String typeSimple) {
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(typeSimple);
Validate.notNull(typeSimple);
return (def instanceof RuntimePrimitiveDatatypeDefinition) || (def instanceof RuntimeCompositeDatatypeDefinition);
}
@Override
public boolean isResource(String typeSimple) {
BaseRuntimeElementDefinition<?> def = myCtx.getElementDefinition(typeSimple);
Validate.notNull(typeSimple);
return def instanceof RuntimeResourceDefinition;
}
@Override
public boolean hasLinkFor(String typeSimple) {
return false;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return null;
}
@Override
public BindingResolution resolveBinding(org.hl7.fhir.r4.model.StructureDefinition def, ElementDefinition.ElementDefinitionBindingComponent binding, String path) throws FHIRException {
return null;
}
@Override
public String getLinkForProfile(org.hl7.fhir.r4.model.StructureDefinition profile, String url) {
return null;
}
@Override
public boolean prependLinks() {
return false;
}
}
}

View File

@ -2,7 +2,6 @@ package org.hl7.fhir.r4.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.CodeSystem;
@ -28,7 +27,7 @@ public class ValidationSupportChain implements IValidationSupport {
* Constructor
*/
public ValidationSupportChain() {
myChain = new ArrayList<IValidationSupport>();
myChain = new ArrayList<>();
}
/**
@ -61,19 +60,19 @@ public class ValidationSupportChain implements IValidationSupport {
throw new InvalidRequestException("unable to find code system " + theInclude.getSystem());
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
List<IBaseResource> retVal = new ArrayList<>();
for (IValidationSupport next : myChain) {
List<IBaseResource> candidates = next.fetchAllConformanceResources(theContext);
if (candidates != null) {
retVal.addAll(candidates);
}
}
return retVal;
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
List<IBaseResource> retVal = new ArrayList<>();
for (IValidationSupport next : myChain) {
List<IBaseResource> candidates = next.fetchAllConformanceResources(theContext);
if (candidates != null) {
retVal.addAll(candidates);
}
}
return retVal;
}
@Override
@Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theSystem)) {
@ -129,11 +128,23 @@ public class ValidationSupportChain implements IValidationSupport {
return false;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theProfileName) {
StructureDefinition outcome = null;
for (IValidationSupport next : myChain) {
outcome = next.generateSnapshot(theInput, theUrl, theProfileName);
if (outcome != null) {
break;
}
}
return outcome;
}
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size());
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
@ -150,12 +161,15 @@ public class ValidationSupportChain implements IValidationSupport {
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
ArrayList<StructureDefinition> retVal = new ArrayList<StructureDefinition>();
Set<String> urls= new HashSet<String>();
ArrayList<StructureDefinition> retVal = new ArrayList<>();
Set<String> urls = new HashSet<>();
for (IValidationSupport nextSupport : myChain) {
for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) {
if (isBlank(next.getUrl()) || urls.add(next.getUrl())) {
retVal.add(next);
List<StructureDefinition> list = nextSupport.fetchAllStructureDefinitions(theContext);
if (list != null) {
for (StructureDefinition next : list) {
if (isBlank(next.getUrl()) || urls.add(next.getUrl())) {
retVal.add(next);
}
}
}
}

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
public class ${className}ResourceProvider extends
## We have specialized base classes for RPs that handle certain resource types. These
## RPs implement type specific operations
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap') || ${className} == 'MessageHeader' || ${className} == 'Composition'))
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap') || ${className} == 'MessageHeader' || ${className} == 'Composition' || ${className} == 'StructureDefinition'))
BaseJpaResourceProvider${className}${versionCapitalized}
#else
JpaResourceProvider${versionCapitalized}<${className}>

View File

@ -66,7 +66,7 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
IFhirResourceDaoConceptMap<org.hl7.fhir.dstu3.model.ConceptMap>
#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ConceptMap' )
IFhirResourceDaoConceptMap<org.hl7.fhir.r4.model.ConceptMap>
#elseif ( ${versionCapitalized} != 'Dstu1' && (${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'MessageHeader'))
#elseif ( ${versionCapitalized} != 'Dstu1' && (${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'MessageHeader' || ${res.name} == 'StructureDefinition'))
IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}>
#else
IFhirResourceDao<${resourcePackage}.${res.declaringClassNameComplete}>
@ -76,7 +76,7 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ConceptMap' )
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#elseif ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem' || ${res.name} == 'MessageHeader' || ${res.name} == 'Composition')
#elseif ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem' || ${res.name} == 'MessageHeader' || ${res.name} == 'Composition' || ${res.name} == 'StructureDefinition')
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#else
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${versionCapitalized}<${resourcePackage}.${res.declaringClassNameComplete}> retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${versionCapitalized}<${resourcePackage}.${res.declaringClassNameComplete}>();

View File

@ -880,7 +880,7 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>

View File

@ -279,6 +279,11 @@
Added a new Pointcut STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING that is called at the start of
the expungeEverything operation.
</action>
<action type="add">
The JPA server now has the ability to generate snapshot profiles from differential
profiles via the $snapshot operation, and will automatically generate a snapshot when
needed for validation.
</action>
</release>
<release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">