Sync with upstream/master

This commit is contained in:
Matti Uusitalo 2018-12-17 15:38:05 +02:00
commit 98f929997b
21 changed files with 888 additions and 66 deletions

View File

@ -53,8 +53,8 @@ public class FhirTerser {
if (theChildDefinition == null) if (theChildDefinition == null)
return null; return null;
if (theCurrentList == null || theCurrentList.isEmpty()) if (theCurrentList == null || theCurrentList.isEmpty())
return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName())); return new ArrayList<>(Arrays.asList(theChildDefinition.getElementName()));
List<String> newList = new ArrayList<String>(theCurrentList); List<String> newList = new ArrayList<>(theCurrentList);
newList.add(theChildDefinition.getElementName()); newList.add(theChildDefinition.getElementName());
return newList; return newList;
} }

View File

@ -174,20 +174,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
@Autowired @Autowired
private ISearchDao mySearchDao; private ISearchDao mySearchDao;
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc; private ISearchParamPresenceSvc mySearchParamPresenceSvc;
//@Autowired //@Autowired
//private ISearchResultDao mySearchResultDao; //private ISearchResultDao mySearchResultDao;
@Autowired @Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
private BeanFactory beanFactory; private BeanFactory beanFactory;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired @Autowired
private SearchParamExtractorService mySearchParamExtractorService;
@Autowired
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor; private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
@Autowired @Autowired
private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer; private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer;

View File

@ -58,4 +58,6 @@ public interface IResourceReindexingSvc {
* to be used by unit tests. * to be used by unit tests.
*/ */
void cancelAndPurgeAllJobs(); void cancelAndPurgeAllJobs();
int countReindexJobs();
} }

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.search.reindex;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -30,11 +30,11 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -98,6 +98,8 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
private FhirContext myContext; private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager; private EntityManager myEntityManager;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@VisibleForTesting @VisibleForTesting
void setReindexJobDaoForUnitTest(IResourceReindexJobDao theReindexJobDao) { void setReindexJobDaoForUnitTest(IResourceReindexJobDao theReindexJobDao) {
@ -186,7 +188,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
runReindexingPass(); runReindexingPass();
} }
@Override @Override
@Transactional(Transactional.TxType.NEVER) @Transactional(Transactional.TxType.NEVER)
public Integer runReindexingPass() { public Integer runReindexingPass() {
@ -203,7 +204,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return null; return null;
} }
private Integer doReindexingPassInsideLock() { private int doReindexingPassInsideLock() {
expungeJobsMarkedAsDeleted(); expungeJobsMarkedAsDeleted();
return runReindexJobs(); return runReindexJobs();
} }
@ -233,13 +234,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
} }
private int runReindexJobs() { private int runReindexJobs() {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false)); Collection<ResourceReindexJobEntity> jobs = getResourceReindexJobEntities();
assert jobs != null;
if (jobs.size() > 0) { if (jobs.size() > 0) {
ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs); ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs);
} else { } else {
ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs); ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs);
return 0;
} }
int count = 0; int count = 0;
@ -255,6 +256,17 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return count; return count;
} }
@Override
public int countReindexJobs() {
return getResourceReindexJobEntities().size();
}
private Collection<ResourceReindexJobEntity> getResourceReindexJobEntities() {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
assert jobs != null;
return jobs;
}
private void markJobAsDeleted(ResourceReindexJobEntity theJob) { private void markJobAsDeleted(ResourceReindexJobEntity theJob) {
ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId()); ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId());
myTxTemplate.execute(t -> { myTxTemplate.execute(t -> {
@ -263,6 +275,11 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
}); });
} }
@VisibleForTesting
public void setSearchParamRegistryForUnitTest(ISearchParamRegistry theSearchParamRegistry) {
mySearchParamRegistry = theSearchParamRegistry;
}
private int runReindexJob(ResourceReindexJobEntity theJob) { private int runReindexJob(ResourceReindexJobEntity theJob) {
if (theJob.getSuspendedUntil() != null) { if (theJob.getSuspendedUntil() != null) {
if (theJob.getSuspendedUntil().getTime() > System.currentTimeMillis()) { if (theJob.getSuspendedUntil().getTime() > System.currentTimeMillis()) {
@ -274,6 +291,15 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
AtomicInteger counter = new AtomicInteger(); AtomicInteger counter = new AtomicInteger();
/*
* On the first time we run a particular reindex job, let's make sure we
* have the latest search parameters loaded. This is good since a common reason to
* be reindexing is that the search parameters have changed in some way.
*/
if (theJob.getThresholdLow() == null) {
mySearchParamRegistry.forceRefresh();
}
// Calculate range // Calculate range
Date low = theJob.getThresholdLow() != null ? theJob.getThresholdLow() : BEGINNING_OF_TIME; Date low = theJob.getThresholdLow() != null ? theJob.getThresholdLow() : BEGINNING_OF_TIME;
Date high = theJob.getThresholdHigh(); Date high = theJob.getThresholdHigh();
@ -461,7 +487,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceTable.getResourceType()); IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceTable.getResourceType());
long expectedVersion = resourceTable.getVersion(); long expectedVersion = resourceTable.getVersion();
IBaseResource resource = dao.read(resourceTable.getIdDt().toVersionless(), null,true); IBaseResource resource = dao.read(resourceTable.getIdDt().toVersionless(), null, true);
if (resource == null) { if (resource == null) {
throw new InternalErrorException("Could not find resource version " + resourceTable.getIdDt().toUnqualified().getValue() + " in database"); throw new InternalErrorException("Could not find resource version " + resourceTable.getIdDt().toUnqualified().getValue() + " in database");
} }

View File

@ -10,6 +10,8 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
@ -17,15 +19,13 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
@ -38,6 +38,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -236,6 +237,32 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
} }
@Test @Test
public void testCreateReflexResourceTheHardWay() throws IOException, ServletException {
ServerOperationInterceptorAdapter interceptor = new ReflexInterceptor();
ourRestServer.registerInterceptor(interceptor);
try {
Patient p = new Patient();
p.setActive(true);
IIdType pid = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
Bundle observations = ourClient
.search()
.forResource("Observation")
.where(Observation.SUBJECT.hasId(pid))
.returnBundle(Bundle.class)
.execute();
assertEquals(1, observations.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(observations));
} finally {
ourRestServer.unregisterInterceptor(interceptor);
}
}
@Test
public void testCreateResourceWithVersionedReference() throws IOException, ServletException { public void testCreateResourceWithVersionedReference() throws IOException, ServletException {
String methodName = "testCreateResourceWithVersionedReference"; String methodName = "testCreateResourceWithVersionedReference";
@ -353,6 +380,26 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
} }
} }
public class ReflexInterceptor extends ServerOperationInterceptorAdapter {
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (theResource instanceof Patient) {
((ServletRequestDetails) theRequest).getServletRequest().setAttribute("CREATED_PATIENT", theResource);
}
}
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
Patient createdPatient = (Patient) theRequestDetails.getServletRequest().getAttribute("CREATED_PATIENT");
if (createdPatient != null) {
Observation observation = new Observation();
observation.setSubject(new Reference(createdPatient.getId()));
ourClient.create().resource(observation).execute();
}
}
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -63,6 +64,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
@Captor @Captor
private ArgumentCaptor<Date> myHighCaptor; private ArgumentCaptor<Date> myHighCaptor;
private ResourceReindexJobEntity mySingleJob; private ResourceReindexJobEntity mySingleJob;
@Mock
private ISearchParamRegistry mySearchParamRegistry;
@Override @Override
protected FhirContext getContext() { protected FhirContext getContext() {
@ -87,6 +90,7 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
mySvc.setReindexJobDaoForUnitTest(myReindexJobDao); mySvc.setReindexJobDaoForUnitTest(myReindexJobDao);
mySvc.setResourceTableDaoForUnitTest(myResourceTableDao); mySvc.setResourceTableDaoForUnitTest(myResourceTableDao);
mySvc.setTxManagerForUnitTest(myTxManager); mySvc.setTxManagerForUnitTest(myTxManager);
mySvc.setSearchParamRegistryForUnitTest(mySearchParamRegistry);
mySvc.start(); mySvc.start();
} }
@ -175,6 +179,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
verify(myReindexJobDao, times(1)).getReindexCount(any()); verify(myReindexJobDao, times(1)).getReindexCount(any());
verify(myReindexJobDao, times(1)).setReindexCount(any(), anyInt()); verify(myReindexJobDao, times(1)).setReindexCount(any(), anyInt());
verifyNoMoreInteractions(myReindexJobDao); verifyNoMoreInteractions(myReindexJobDao);
verify(mySearchParamRegistry, times(1)).forceRefresh();
} }
@Test @Test

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -583,6 +585,50 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
} }
} }
/**
* Check that a custom ValueSet against a custom CodeSystem expands correctly
*/
@Test
public void testCustomValueSetExpansion() {
CodeSystem cs= new CodeSystem();
cs.setUrl("http://codesystems-r-us");
cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
IIdType csId = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
TermCodeSystemVersion version = new TermCodeSystemVersion();
version.getConcepts().add(new TermConcept(version, "A"));
version.getConcepts().add(new TermConcept(version, "B"));
version.getConcepts().add(new TermConcept(version, "C"));
version.getConcepts().add(new TermConcept(version, "D"));
runInTransaction(()->{
ResourceTable resTable = myEntityManager.find(ResourceTable.class, csId.getIdPartAsLong());
version.setResource(resTable);
myTermSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", version);
});
org.hl7.fhir.dstu3.model.ValueSet vs = new org.hl7.fhir.dstu3.model.ValueSet();
vs.setUrl("http://valuesets-r-us");
vs.getCompose()
.addInclude()
.setSystem(cs.getUrl())
.addConcept(new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode("A"))
.addConcept(new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode("C"));
myValueSetDao.create(vs);
org.hl7.fhir.dstu3.model.ValueSet expansion = myValueSetDao.expandByIdentifier(vs.getUrl(), null);
List<String> expansionCodes = expansion
.getExpansion()
.getContains()
.stream()
.map(t -> t.getCode())
.sorted()
.collect(Collectors.toList());
assertEquals(Lists.newArrayList("A","C"), expansionCodes);
}
public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) { public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
List<String> retVal = new ArrayList<>(); List<String> retVal = new ArrayList<>();

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.migrate;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -53,7 +53,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), theTableName, false, true); ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), false, true);
Set<String> indexNames = new HashSet<>(); Set<String> indexNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -81,7 +81,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), theTableName, false, false); ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), false, false);
while (indexes.next()) { while (indexes.next()) {
String indexName = indexes.getString("INDEX_NAME"); String indexName = indexes.getString("INDEX_NAME");
@ -112,7 +112,7 @@ public class JdbcUtils {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
String catalog = connection.getCatalog(); String catalog = connection.getCatalog();
String schema = connection.getSchema(); String schema = connection.getSchema();
ResultSet indexes = metadata.getColumns(catalog, schema, theTableName, null); ResultSet indexes = metadata.getColumns(catalog, schema, massageIdentifier(metadata, theTableName), null);
while (indexes.next()) { while (indexes.next()) {
@ -165,7 +165,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
ResultSet indexes = metadata.getCrossReference(connection.getCatalog(), connection.getSchema(), theTableName, connection.getCatalog(), connection.getSchema(), theForeignTable); ResultSet indexes = metadata.getCrossReference(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theForeignTable));
Set<String> columnNames = new HashSet<>(); Set<String> columnNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -201,7 +201,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
ResultSet indexes = metadata.getColumns(connection.getCatalog(), connection.getSchema(), theTableName, null); ResultSet indexes = metadata.getColumns(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), null);
Set<String> columnNames = new HashSet<>(); Set<String> columnNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -253,7 +253,7 @@ public class JdbcUtils {
} }
} }
return sequenceNames; return sequenceNames;
} catch (SQLException e ) { } catch (SQLException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
}); });
@ -298,7 +298,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
ResultSet tables = metadata.getColumns(connection.getCatalog(), connection.getSchema(), theTableName, theColumnName); ResultSet tables = metadata.getColumns(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), null);
while (tables.next()) { while (tables.next()) {
String tableName = toUpperCase(tables.getString("TABLE_NAME"), Locale.US); String tableName = toUpperCase(tables.getString("TABLE_NAME"), Locale.US);
@ -325,4 +325,14 @@ public class JdbcUtils {
}); });
} }
} }
private static String massageIdentifier(DatabaseMetaData theMetadata, String theCatalog) throws SQLException {
String retVal = theCatalog;
if (theMetadata.storesLowerCaseIdentifiers()) {
retVal = retVal.toLowerCase();
} else {
retVal = retVal.toUpperCase();
}
return retVal;
}
} }

View File

@ -57,10 +57,9 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
@Autowired @Autowired
private ModelConfig myModelConfig; private ModelConfig myModelConfig;
private volatile long myLastRefresh; private volatile long myLastRefresh;
private ApplicationContext myApplicationContext;
private ISearchParamProvider mySearchParamProvider; private ISearchParamProvider mySearchParamProvider;
public BaseSearchParamRegistry(ISearchParamProvider theSearchParamProvider) { BaseSearchParamRegistry(ISearchParamProvider theSearchParamProvider) {
super(); super();
mySearchParamProvider = theSearchParamProvider; mySearchParamProvider = theSearchParamProvider;
} }
@ -128,7 +127,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
return Collections.unmodifiableList(retVal); return Collections.unmodifiableList(retVal);
} }
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() { private Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams; return myBuiltInSearchParams;
} }

View File

@ -66,6 +66,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private Function<IWorkerContext, IEnableWhenEvaluator> enableWhenEvaluatorSupplier = ctx -> new DefaultEnableWhenEvaluator(); private Function<IWorkerContext, IEnableWhenEvaluator> enableWhenEvaluatorSupplier = ctx -> new DefaultEnableWhenEvaluator();
private boolean errorForUnknownProfiles; private boolean errorForUnknownProfiles;
private List<String> extensionDomains = Collections.emptyList();
/** /**
* Constructor * Constructor
@ -87,18 +88,52 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
private String determineResourceName(Document theDocument) { /**
Element root = null; * Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
return this;
}
/**
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes(); NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) { for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) { if (list.item(i) instanceof Element) {
root = (Element) list.item(i); return list.item(i).getLocalName();
break;
} }
} }
root = theDocument.getDocumentElement(); return theDocument.getDocumentElement().getLocalName();
return root.getLocalName();
} }
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) { private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
@ -144,7 +179,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* guielines will be ignored. * guielines will be ignored.
* </p> * </p>
* *
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)} * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
*/ */
public BestPracticeWarningLevel getBestPracticeWarningLevel() { public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel; return myBestPracticeWarningLevel;
@ -260,6 +295,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setNoTerminologyChecks(isNoTerminologyChecks()); v.setNoTerminologyChecks(isNoTerminologyChecks());
v.setMyEnableWhenEvaluator(enableWhenEvaluatorSupplier.apply(wrappedWorkerContext)); v.setMyEnableWhenEvaluator(enableWhenEvaluatorSupplier.apply(wrappedWorkerContext));
v.setErrorForUnknownProfiles(isErrorForUnknownProfiles()); v.setErrorForUnknownProfiles(isErrorForUnknownProfiles());
v.addExtensionDomains(extensionDomains);
List<ValidationMessage> messages = new ArrayList<>(); List<ValidationMessage> messages = new ArrayList<>();
@ -368,7 +404,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private LoadingCache<ResourceKey, org.hl7.fhir.r4.model.Resource> myFetchResourceCache; private LoadingCache<ResourceKey, org.hl7.fhir.r4.model.Resource> myFetchResourceCache;
private org.hl7.fhir.r4.model.Parameters myExpansionProfile; private org.hl7.fhir.r4.model.Parameters myExpansionProfile;
public WorkerContextWrapper(HapiWorkerContext theWorkerContext) { WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext; myWrap = theWorkerContext;
myConverter = new VersionConvertor_30_40(); myConverter = new VersionConvertor_30_40();
@ -449,7 +485,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public void cacheResource(org.hl7.fhir.r4.model.Resource res) throws FHIRException { public void cacheResource(org.hl7.fhir.r4.model.Resource res) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -471,7 +507,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override @Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) { public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
ValueSet convertedSource = null; ValueSet convertedSource;
try { try {
convertedSource = VersionConvertor_30_40.convertValueSet(source); convertedSource = VersionConvertor_30_40.convertValueSet(source);
} catch (FHIRException e) { } catch (FHIRException e) {
@ -495,7 +531,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException { public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -662,7 +698,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public IResourceValidator newValidator() throws FHIRException { public IResourceValidator newValidator() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -687,7 +723,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public boolean supportsSystem(String system) throws TerminologyServiceException { public boolean supportsSystem(String system) {
return myWrap.supportsSystem(system); return myWrap.supportsSystem(system);
} }
@ -704,6 +740,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override @Override
public ValidationResult validateCode(String system, String code, String display) { public ValidationResult validateCode(String system, String code, String display) {
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
// TODO: converted code might be null -> NPE
return convertValidationResult(result); return convertValidationResult(result);
} }
@ -754,6 +791,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result); return convertValidationResult(result);
} }
@ -774,6 +812,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result); return convertValidationResult(result);
} }

View File

@ -32,6 +32,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader; import java.io.StringReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -45,6 +46,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private DocumentBuilderFactory myDocBuilderFactory; private DocumentBuilderFactory myDocBuilderFactory;
private boolean myNoTerminologyChecks; private boolean myNoTerminologyChecks;
private StructureDefinition myStructureDefintion; private StructureDefinition myStructureDefintion;
private List<String> extensionDomains = Collections.emptyList();
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -68,18 +70,52 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
private String determineResourceName(Document theDocument) { /**
Element root = null; * Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
return this;
}
/**
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes(); NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) { for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) { if (list.item(i) instanceof Element) {
root = (Element) list.item(i); return list.item(i).getLocalName();
break;
} }
} }
root = theDocument.getDocumentElement(); return theDocument.getDocumentElement().getLocalName();
return root.getLocalName();
} }
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) { private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
@ -120,8 +156,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored. * guielines will be ignored.
* </p> * </p>
* *
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)} * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
*/ */
public BestPracticeWarningLevel getBestPracticeWarningLevel() { public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel; return myBestPracticeWarningLevel;
@ -211,8 +247,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed()); v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL); v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks()); v.setNoTerminologyChecks(isNoTerminologyChecks());
v.addExtensionDomains(extensionDomains);
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) { if (theEncoding == EncodingEnum.XML) {
Document document; Document document;
@ -312,7 +349,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
public static class NullEvaluationContext implements IEvaluationContext { public static class NullEvaluationContext implements IEvaluationContext {
@Override @Override
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException { public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) {
return null; return null;
} }
@ -327,12 +364,12 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException { public Base resolveConstant(Object theAppContext, String theName) {
return null; return null;
} }
@Override @Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException { public TypeDetails resolveConstantType(Object theAppContext, String theName) {
return null; return null;
} }

View File

@ -420,8 +420,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private boolean allowUnknownExtension(String url) { private boolean allowUnknownExtension(String url) {
if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) if (isPredefinedExtension(url))
// Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
return true; return true;
for (String s : extensionDomains) for (String s : extensionDomains)
if (url.startsWith(s)) if (url.startsWith(s))
@ -430,8 +429,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private boolean isKnownExtension(String url) { private boolean isKnownExtension(String url) {
// Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with if (isPredefinedExtension(url))
if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION))
return true; return true;
for (String s : extensionDomains) for (String s : extensionDomains)
if (url.startsWith(s)) if (url.startsWith(s))
@ -439,6 +437,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false; return false;
} }
private boolean isPredefinedExtension(String url) {
return url.contains("example.org")
|| url.contains("acme.com")
|| url.contains("nema.org")
|| url.startsWith("http://hl7.org/fhir/StructureDefinition/")
|| url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION);
}
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) { private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
if (bpWarnings != null) { if (bpWarnings != null) {
switch (bpWarnings) { switch (bpWarnings) {
@ -1954,6 +1960,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return extensionDomains; return extensionDomains;
} }
public InstanceValidator addExtensionDomains(List<String> extensionDomains) {
this.extensionDomains.addAll(extensionDomains);
return this;
}
private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) { private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) {
String targetUrl = null; String targetUrl = null;
String version = ""; String version = "";

View File

@ -0,0 +1,134 @@
package org.hl7.fhir.dstu3.hapi.validation;
import java.util.Collections;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.ValidationResult;
import org.hamcrest.Matchers;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class QuestionnaireValidatorDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorDstu3Test.class);
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forDstu3();
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
@Before
public void before() {
IValidationSupport myValSupport = mock(IValidationSupport.class);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCoding() {
String[] extensionDomainsToTest = new String[] {
"http://example.org/questionnaire-color-control-1",
"https://example.org/questionnaire-color-control-2",
"http://acme.com/questionnaire-color-control-3",
"https://acme.com/questionnaire-color-control-4",
"http://nema.org/questionnaire-color-control-5",
"https://nema.org/questionnaire-color-control-6",
"http://hl7.org/fhir/StructureDefinition/questionnaire-scoreItem",
"http://hl7.org/fhir/StructureDefinition/structuredefinition-expression",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = new Questionnaire();
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCodeableConcept() {
String[] extensionDomainsToTest = new String[] {
"http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = new Questionnaire();
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new CodeableConcept().addCoding(new Coding(null, "text-box", null)));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithCustomExtensionDomain() {
Questionnaire q = new Questionnaire();
String extensionUrl = "http://my.own.domain/StructureDefinition/";
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionUrl + "questionnaire-itemControl")
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.hasSize(1));
assertEquals(errors.getMessages().get(0).getSeverity(), ResultSeverityEnum.INFORMATION);
assertThat(errors.getMessages().get(0).getMessage(), Matchers.startsWith("Unknown extension " + extensionUrl));
myInstanceVal.setCustomExtensionDomains(Collections.singletonList(extensionUrl));
errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,145 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.ValidationResult;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class QuestionnaireValidatorR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorR4Test.class);
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forR4();
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
@Before
public void before() {
IValidationSupport myValSupport = mock(IValidationSupport.class);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomains() {
String[] extensionDomainsToTest = new String[] {
"http://example.org/questionnaire-color-control-1",
"https://example.org/questionnaire-color-control-2",
"http://acme.com/questionnaire-color-control-3",
"https://acme.com/questionnaire-color-control-4",
"http://nema.org/questionnaire-color-control-5",
"https://nema.org/questionnaire-color-control-6",
"http://hl7.org/fhir/StructureDefinition/questionnaire-scoreItem",
"http://hl7.org/fhir/StructureDefinition/structuredefinition-expression",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCodeableConcept() {
String[] extensionDomainsToTest = new String[] {
"http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new CodeableConcept().addCoding(new Coding(null, "text-box", null)));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithCustomExtensionDomain() {
String extensionUrl = "http://my.own.domain/StructureDefinition/";
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionUrl + "questionnaire-itemControl")
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.hasSize(1));
assertEquals(errors.getMessages().get(0).getSeverity(), ResultSeverityEnum.INFORMATION);
assertThat(errors.getMessages().get(0).getMessage(), Matchers.startsWith("Unknown extension " + extensionUrl));
myInstanceVal.setCustomExtensionDomains(extensionUrl);
errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
private Questionnaire minimalValidQuestionnaire() {
Narrative n = new Narrative().setStatus(NarrativeStatus.GENERATED);
n.setDivAsString("simple example");
Questionnaire q = new Questionnaire();
q.setText(n);
q.setName("SomeName");
q.setStatus(PublicationStatus.ACTIVE);
return q;
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.tinder;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import java.io.File;
import java.util.List;
/**
* Base class for mojo generatorss.
*/
public abstract class AbstractGeneratorMojo extends AbstractMojo {
protected final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(getClass());
@Parameter(required = true, defaultValue = "${project.build.directory}/..")
protected String baseDir;
@Parameter
protected String packageBase = "";
@Parameter
protected List<String> baseResourceNames;
@Parameter
protected List<String> excludeResourceNames;
@Parameter
protected String templateName;
@Parameter(required = true)
protected String version;
@Component
protected MavenProject myProject;
@Override
public final void execute() throws MojoExecutionException, MojoFailureException {
doExecute(new Configuration(this.version, baseDir, getTargetDirectory(), this.packageBase, this.baseResourceNames, this.excludeResourceNames));
}
protected abstract void doExecute(Configuration mavenGeneratorConfiguration) throws MojoExecutionException, MojoFailureException;
protected abstract File getTargetDirectory();
}

View File

@ -0,0 +1,134 @@
package ca.uhn.fhir.tinder;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser;
import org.apache.commons.lang.WordUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.TreeSet;
public class Configuration {
private final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Configuration.class);
private String version;
private File targetDirectory;
private String packageSuffix;
private String packageBase;
private FhirContext fhirContext;
private File packageDirectoryBase;
private final List<String> resourceNames = new ArrayList<>();
private String baseDir;
public Configuration(String version, String baseDir, File targetDirectory, String packageBase, List<String> baseResourceNames, List<String> excludeResourceNames) {
this.targetDirectory = targetDirectory;
this.packageBase = packageBase;
this.packageDirectoryBase = new File(targetDirectory, packageBase.replace(".", File.separatorChar + ""));
switch (version) {
case "dstu2":
fhirContext = FhirContext.forDstu2();
break;
case "dstu3":
fhirContext = FhirContext.forDstu3();
packageSuffix = ".dstu3";
break;
case "r4":
fhirContext = FhirContext.forR4();
packageSuffix = ".r4";
break;
default:
throw new IllegalArgumentException("Unknown version configured: " + version);
}
this.version = version;
if (baseResourceNames == null || baseResourceNames.isEmpty()) {
ourLog.info("No resource names supplied, going to use all resources from version: {}", fhirContext.getVersion().getVersion());
Properties p = new Properties();
try {
p.load(fhirContext.getVersion().getFhirVersionPropertiesFile());
} catch (IOException e) {
throw new IllegalArgumentException("Failed to load version property file", e);
}
ourLog.debug("Property file contains: {}", p);
TreeSet<String> keys = new TreeSet<String>();
for (Object next : p.keySet()) {
keys.add((String) next);
}
for (String next : keys) {
if (next.startsWith("resource.")) {
resourceNames.add(next.substring("resource.".length()).toLowerCase());
}
}
if (fhirContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
resourceNames.remove("conformance");
}
} else {
for (String resourceName : baseResourceNames) {
resourceNames.add(resourceName.toLowerCase());
}
}
if (excludeResourceNames != null) {
for (String resourceName : excludeResourceNames) {
resourceNames.remove(resourceName.toLowerCase());
}
}
ourLog.info("Including the following resources: {}", resourceNames);
}
public File getPackageDirectoryBase() {
return packageDirectoryBase;
}
public String getPackageSuffix() {
return packageSuffix;
}
public List<String> getResourceNames() {
return resourceNames;
}
public String getPackageBase() {
return packageBase;
}
public String getVersion() {
return version;
}
public String getResourcePackage() {
if (BaseStructureSpreadsheetParser.determineVersionEnum(version).isRi()) {
return "org.hl7.fhir." + version + ".model";
}
return "ca.uhn.fhir.model." + version + ".resource";
}
public String getVersionCapitalized() {
String capitalize = WordUtils.capitalize(version);
if ("Dstu".equals(capitalize)) {
return "Dstu1";
}
return capitalize;
}
public File getTargetDirectory() {
return targetDirectory;
}
public String getBaseDir() {
return baseDir;
}
}

View File

@ -0,0 +1,75 @@
package ca.uhn.fhir.tinder;
import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingModel;
import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingSpreadsheet;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.tools.generic.EscapeTool;
import java.io.*;
@Mojo(name = "generate-resource", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
public class TinderResourceGeneratorMojo extends AbstractGeneratorMojo {
@Parameter(required = true, defaultValue = "${project.build.directory}/generated-resources/tinder")
protected File targetDirectory;
@Parameter(required = true)
protected String fileName = "";
@Override
protected void doExecute(Configuration configuration) throws MojoExecutionException, MojoFailureException {
File packageDirectoryBase = configuration.getPackageDirectoryBase();
packageDirectoryBase.mkdirs();
ResourceGeneratorUsingModel gen = new ResourceGeneratorUsingModel(configuration.getVersion(), configuration.getBaseDir());
gen.setBaseResourceNames(configuration.getResourceNames());
try {
gen.parse();
VelocityContext ctx = new VelocityContext();
ctx.put("resources", gen.getResources());
ctx.put("packageBase", configuration.getPackageBase());
ctx.put("version", configuration.getVersion());
ctx.put("package_suffix", configuration.getPackageSuffix());
ctx.put("esc", new EscapeTool());
ctx.put("resourcePackage", configuration.getResourcePackage());
ctx.put("versionCapitalized", configuration.getVersionCapitalized());
VelocityEngine v = new VelocityEngine();
v.setProperty("resource.loader", "cp");
v.setProperty("cp.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
v.setProperty("runtime.references.strict", Boolean.TRUE);
InputStream templateIs = ResourceGeneratorUsingSpreadsheet.class.getResourceAsStream(templateName);
InputStreamReader templateReader = new InputStreamReader(templateIs);
File file = new File(packageDirectoryBase, fileName);
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(file, false), "UTF-8");
v.evaluate(ctx, w, "", templateReader);
w.close();
Resource resource = new Resource();
resource.setDirectory(packageDirectoryBase.getAbsolutePath());
//resource.setDirectory(targetDirectory.getAbsolutePath());
//resource.addInclude(packageBase);
myProject.addResource(resource);
} catch (Exception e) {
throw new MojoFailureException("Failed to generate resources", e);
}
}
@Override
public File getTargetDirectory() {
return targetDirectory;
}
}

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.tinder;
import java.io.*;
import java.util.*;
import org.apache.maven.plugin.*;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.project.MavenProject;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.tinder.parser.*;
@Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class TinderSourcesGeneratorMojo extends AbstractGeneratorMojo {
@Parameter(required = true, defaultValue = "${project.build.directory}/generated-sources/tinder")
protected File targetDirectory;
@Parameter
private String filenameSuffix = "ResourceProvider";
@Parameter
private String filenamePrefix = "";
@Override
public void doExecute(Configuration configuration) throws MojoExecutionException, MojoFailureException {
File packageDirectoryBase = configuration.getPackageDirectoryBase();
packageDirectoryBase.mkdirs();
ResourceGeneratorUsingModel gen = new ResourceGeneratorUsingModel(configuration.getVersion(), configuration.getBaseDir());
gen.setBaseResourceNames(configuration.getResourceNames());
try {
gen.parse();
gen.setFilenameSuffix(filenameSuffix);
gen.setFilenamePrefix(filenamePrefix);
gen.setTemplate(templateName);
gen.writeAll(packageDirectoryBase, null, configuration.getPackageBase());
} catch (Exception e) {
throw new MojoFailureException("Failed to generate server", e);
}
myProject.addCompileSourceRoot(configuration.getTargetDirectory().getAbsolutePath());
}
@Override
protected File getTargetDirectory() {
return targetDirectory;
}
}

View File

@ -711,7 +711,7 @@ public abstract class BaseStructureParser {
return null; return null;
} }
public static FhirVersionEnum determineVersionEnum(String version) throws MojoFailureException { public static FhirVersionEnum determineVersionEnum(String version) {
FhirVersionEnum versionEnum; FhirVersionEnum versionEnum;
if ("dstu2".equals(version)) { if ("dstu2".equals(version)) {
versionEnum = FhirVersionEnum.DSTU2; versionEnum = FhirVersionEnum.DSTU2;
@ -720,7 +720,7 @@ public abstract class BaseStructureParser {
} else if ("r4".equals(version)) { } else if ("r4".equals(version)) {
versionEnum = FhirVersionEnum.R4; versionEnum = FhirVersionEnum.R4;
} else { } else {
throw new MojoFailureException("Unknown version: " + version); throw new IllegalArgumentException("Unknown version: " + version);
} }
return versionEnum; return versionEnum;
} }

View File

@ -518,7 +518,7 @@
<jaxb_api_version>2.3.0</jaxb_api_version> <jaxb_api_version>2.3.0</jaxb_api_version>
<jaxb_core_version>2.3.0</jaxb_core_version> <jaxb_core_version>2.3.0</jaxb_core_version>
<jersey_version>2.25.1</jersey_version> <jersey_version>2.25.1</jersey_version>
<jetty_version>9.4.12.v20180830</jetty_version> <jetty_version>9.4.14.v20181114</jetty_version>
<jsr305_version>3.0.2</jsr305_version> <jsr305_version>3.0.2</jsr305_version>
<!--<hibernate_version>5.2.10.Final</hibernate_version>--> <!--<hibernate_version>5.2.10.Final</hibernate_version>-->
<hibernate_version>5.3.6.Final</hibernate_version> <hibernate_version>5.3.6.Final</hibernate_version>

View File

@ -98,9 +98,11 @@
or authorizing the contents of the response. or authorizing the contents of the response.
</action> </action>
<action type="add"> <action type="add">
JPA Migrator tool enhancements:
An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against
Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL
statements will be logged at the end of the run. statements will be logged at the end of the run. Also, a case sensitivity issue when running against
some Postgres databases has been corrected.
</action> </action>
<action type="add"> <action type="add">
In the JPA server, when performing a chained reference search on a search parameter with In the JPA server, when performing a chained reference search on a search parameter with
@ -142,6 +144,18 @@
causes Media resources to be served as raw content if the client explicitly requests causes Media resources to be served as raw content if the client explicitly requests
the correct content type cia the Accept header. the correct content type cia the Accept header.
</action> </action>
<action type="add" issue="917">
A new configuration item has been added to the FhirInstanceValidator that
allows you to specify additional "known extension domains", meaning
domains in which the validator will not complain about when it
encounters new extensions. Thanks to Heinz-Dieter Conradi for the
pull request!
</action>
<action type="fix">
Under some circumstances, when a custom search parameter was added to the JPA server
resources could start reindexing before the new search parameter had been saved, meaning that
it was not applied to all resources. This has been corrected.
</action>
</release> </release>
<release version="3.6.0" date="2018-11-12" description="Food"> <release version="3.6.0" date="2018-11-12" description="Food">
<action type="add"> <action type="add">