Merge remote-tracking branch 'origin/master'

This commit is contained in:
Ken Stevens 2019-04-16 12:01:34 -04:00
commit 36f81731be
53 changed files with 575 additions and 488 deletions

View File

@ -33,7 +33,7 @@ public interface IRestfulClient extends IBasicClient {
/**
* The "@Search" annotation indicates that this method supports the
* search operation. You may have many different method annotated with
* search operation. You may have many different methods annotated with
* this annotation, to support many different search criteria. This
* example searches by family name.
*

View File

@ -55,7 +55,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider {
/**
* The "@Search" annotation indicates that this method supports the
* search operation. You may have many different method annotated with
* search operation. You may have many different methods annotated with
* this annotation, to support many different search criteria. This
* example searches by family name.
*

View File

@ -56,7 +56,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
/**
* The "@Search" annotation indicates that this method supports the
* search operation. You may have many different method annotated with
* search operation. You may have many different methods annotated with
* this annotation, to support many different search criteria. This
* example searches by family name.
*

View File

@ -234,6 +234,12 @@ public class ValidatorExamples {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
// TODO: implement
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
// TODO: implement

View File

@ -19,7 +19,11 @@ package ca.uhn.fhir.rest.annotation;
* limitations under the License.
* #L%
*/
import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Denotes a parameter for a REST method which will contain the resource actually
@ -41,8 +45,11 @@ import java.lang.annotation.*;
* have multiple parameters with this annotation, so you can have one parameter
* which accepts the parsed resource, and another which accepts the raw request.
* </p>
* <p>
* Also note that this parameter may be null if a client does not supply a body.
* </p>
*/
@Target(value=ElementType.PARAMETER)
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceParam {

View File

@ -217,28 +217,20 @@ public class PortUtil {
}
private static boolean isAvailable(int port) {
ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(port);
ourLog.info("Testing a bind on port {}", port);
try (ServerSocket ss = new ServerSocket(port)) {
ss.setReuseAddress(true);
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return true;
try (DatagramSocket ds = new DatagramSocket(port)) {
ds.setReuseAddress(true);
ourLog.info("Successfully bound port {}", port);
return true;
} catch (IOException e) {
ourLog.info("Failed to bind port {}: {}", port, e.toString());
return false;
}
} catch (IOException e) {
ourLog.info("Failed to bind port {}: {}", port, e.toString());
return false;
} finally {
if (ds != null) {
ds.close();
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
/* should not be thrown */
}
}
}
}

View File

@ -89,6 +89,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedIdNo
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidParameterChain=Invalid parameter chain: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidVersion=Version "{0}" is not valid for resource {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.missingBody=No body was supplied in request
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed.
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms

View File

@ -63,13 +63,23 @@ public class PortUtilTest {
for (int j = 0; j < portsPerTaskCount; j++) {
int nextFreePort = portUtil.getNextFreePort();
boolean bound;
try (ServerSocket ss = new ServerSocket()) {
ss.bind(new InetSocketAddress("localhost", nextFreePort));
bound = true;
} catch (IOException e) {
String msg = "Failure binding new port " + nextFreePort + ": " + e.toString();
ourLog.error(msg, e);
errors.add(msg);
bound = false;
}
if (!bound) {
try (ServerSocket ss = new ServerSocket()) {
Thread.sleep(1000);
ss.bind(new InetSocketAddress("localhost", nextFreePort));
} catch (Exception e) {
String msg = "Failure binding new port (second attempt) " + nextFreePort + ": " + e.toString();
ourLog.error(msg, e);
errors.add(msg);
}
}
ports.add(nextFreePort);

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -56,6 +57,11 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
String resName = myCtx.getResourceDefinition(theClass).getName();

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
@ -58,6 +59,11 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
String resName = myCtx.getResourceDefinition(theClass).getName();

View File

@ -67,6 +67,11 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
return fetchResource(theContext, CodeSystem.class, theSystem);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return fetchResource(theContext, ValueSet.class, theSystem);
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {

View File

@ -73,14 +73,17 @@ public abstract class BaseConfig implements SchedulingConfigurer {
@Autowired
protected Environment myEnv;
@Autowired
protected DaoRegistry myDaoRegistry;
@Override
public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) {
theTaskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean("myDaoRegistry")
public DaoRegistry daoRegistry() {
return new DaoRegistry();
}
@Bean(autowire = Autowire.BY_TYPE)
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return new DatabaseBackedPagingProvider();
@ -182,7 +185,7 @@ public abstract class BaseConfig implements SchedulingConfigurer {
* Subclasses may override
*/
protected boolean isSupported(String theResourceType) {
return myDaoRegistry.getResourceDao(theResourceType) != null;
return daoRegistry().getResourceDao(theResourceType) != null;
}
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {

View File

@ -142,6 +142,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTimestamp, RequestDetails theRequestDetails) {
if (theResource == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
throw new InvalidRequestException(msg);
}
if (isNotBlank(theResource.getIdElement().getIdPart())) {
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
@ -1270,6 +1275,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails) {
if (theResource == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
throw new InvalidRequestException(msg);
}
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);

View File

@ -35,13 +35,20 @@ import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component("myDaoRegistry")
public class DaoRegistry implements ApplicationContextAware {
private ApplicationContext myAppCtx;
@Autowired
private FhirContext myContext;
/**
* Constructor
*/
public DaoRegistry() {
super();
}
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
private volatile IFhirSystemDao<?, ?> mySystemDao;
private Set<String> mySupportedResourceTypes;

View File

@ -89,6 +89,14 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
return fetchResource(theCtx, CodeSystem.class, theSystem);
}
@Override
public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) {
if (isBlank(theSystem)) {
return null;
}
return fetchResource(theCtx, ValueSet.class, theSystem);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {

View File

@ -87,7 +87,7 @@ public class SearchParamWithInlineReferencesExtractor {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) {
public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams) {
mySearchParamExtractorService.extractFromResource(theParams, theEntity, theResource);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
@ -104,7 +104,7 @@ public class SearchParamWithInlineReferencesExtractor {
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
*/
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
for (Iterator<ResourceLink> existingLinkIter = theExistingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
ResourceLink nextExisting = existingLinkIter.next();
if (theParams.myLinks.remove(nextExisting)) {
existingLinkIter.remove();
@ -263,6 +263,7 @@ public class SearchParamWithInlineReferencesExtractor {
myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().remove(next);
}
boolean haveNewParams = false;
for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myCompositeStringUniques, existingParams.myCompositeStringUniques)) {
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
@ -273,6 +274,12 @@ public class SearchParamWithInlineReferencesExtractor {
}
ourLog.debug("Persisting unique index: {}", next);
myEntityManager.persist(next);
haveNewParams = true;
}
if (theParams.myCompositeStringUniques.size() > 0 || haveNewParams) {
theEntity.setParamsCompositeStringUniquePresent(true);
} else {
theEntity.setParamsCompositeStringUniquePresent(false);
}
}
}

View File

@ -86,6 +86,11 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
return fetchResource(theCtx, CodeSystem.class, theSystem);
}
@Override
public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) {
return fetchResource(theCtx, ValueSet.class, theSystem);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {

View File

@ -204,6 +204,12 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
return null;
}
@CoverageIgnore
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return null;

View File

@ -155,6 +155,12 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
return null;
}
@CoverageIgnore
@Override
public ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return null;

View File

@ -887,6 +887,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
* Select the version from HFJ_RES_VER
* Select the current token indexes
*/
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); // Add an entry to HFJ_RES_VER

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@ -33,7 +35,9 @@ import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -503,7 +507,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId));
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F"+ practId.getIdPart() +"'",
"IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'",
"HASH_SYS_AND_VALUE in ('6795110643554413877')"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
@ -773,11 +777,23 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
// The third pass has a low of (Coverage.lastUpdated + 1ms)
assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
ourLog.info("** Uniques: {}", uniques);
assertEquals(uniques.toString(), 1, uniques.size());
assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Coverage?beneficiary=Patient%2F" + id2.getIdPart() + "&identifier=urn%3Afoo%3Abar%7C123", uniques.get(0).getIndexString());
runInTransaction(() -> {
List<ResourceTable> tables = myResourceTableDao.findAll();
String resourceIds = tables.stream().map(t -> t.getIdDt().getValue()).collect(Collectors.joining(", "));
// 1 patient, 1 coverage, 3 search parameters
assertEquals(resourceIds, 5, tables.size());
for (int i = 0; i < tables.size(); i++) {
assertEquals(INDEX_STATUS_INDEXED, tables.get(i).getIndexStatus().intValue());
}
});
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
ourLog.info("** Uniques: {}", uniques);
assertEquals(uniques.toString(), 1, uniques.size());
assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Coverage?beneficiary=Patient%2F" + id2.getIdPart() + "&identifier=urn%3Afoo%3Abar%7C123", uniques.get(0).getIndexString());
});
}

View File

@ -8,7 +8,9 @@ import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Sets;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.*;
@ -34,6 +36,7 @@ public class SearchParamExtractorR4Test {
@Before
public void before() {
mySearchParamRegistry = new ISearchParamRegistry() {
@Override
public void forceRefresh() {
@ -52,11 +55,12 @@ public class SearchParamExtractorR4Test {
@Override
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName);
Map<String, RuntimeSearchParam> sps = new HashMap<>();
RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName);
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
sps.put(nextSp.getName(), nextSp);
}
return sps;
}
@ -130,6 +134,21 @@ public class SearchParamExtractorR4Test {
assertEquals("Consent/999", ((Reference) links.get(0).getRef()).getReference());
}
@Test
public void testExtensionContainingReference() {
String path = "Patient.extension('http://patext').value.as(Reference)";
RuntimeSearchParam sp = new RuntimeSearchParam("extpat", "Patient SP", path, RestSearchParameterTypeEnum.REFERENCE, new HashSet<>(), Sets.newHashSet("Patient"), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE);
Patient patient = new Patient();
patient.addExtension("http://patext", new Reference("Organization/AAA"));
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
List<PathAndRef> links = extractor.extractResourceLinks(patient, sp);
assertEquals(1, links.size());
}
@Test
public void testExtractComponentQuantities() {
Observation o1 = new Observation();

View File

@ -267,6 +267,84 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testSearchWithDeepChain() throws IOException {
SearchParameter sp = new SearchParameter();
sp.addBase("Patient");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.setType(Enumerations.SearchParamType.REFERENCE);
sp.setCode("extpatorg");
sp.setName("extpatorg");
sp.setExpression("Patient.extension('http://patext').value.as(Reference)");
ourClient.create().resource(sp).execute();
sp = new SearchParameter();
sp.addBase("Organization");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.setType(Enumerations.SearchParamType.REFERENCE);
sp.setCode("extorgorg");
sp.setName("extorgorg");
sp.setExpression("Organization.extension('http://orgext').value.as(Reference)");
ourClient.create().resource(sp).execute();
mySearchParamRegistry.forceRefresh();
Organization grandParent = new Organization();
grandParent.setName("GRANDPARENT");
IIdType grandParentId = ourClient.create().resource(grandParent).execute().getId().toUnqualifiedVersionless();
Organization parent = new Organization();
parent.setName("PARENT");
parent.getPartOf().setReference(grandParentId.getValue());
parent.addExtension("http://orgext", new Reference().setReference(grandParentId.getValue()));
IIdType parentId = ourClient.create().resource(parent).execute().getId().toUnqualifiedVersionless();
Organization org = new Organization();
org.setName("ORGANIZATION");
org.getPartOf().setReference(parentId.getValue());
org.addExtension("http://orgext", new Reference().setReference(parentId.getValue()));
IIdType orgId = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless();
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
myCaptureQueriesListener.clear();
Patient p = new Patient();
p.getManagingOrganization().setReference(orgId.getValue());
p.addExtension("http://patext", new Reference().setReference(orgId.getValue()));
String pid = ourClient.create().resource(p).execute().getId().toUnqualified().getValue();
List<String> idValues;
// Regular search param
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization=" + orgId.getValue());
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.name=ORGANIZATION");
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.partof.name=PARENT");
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.partof.partof.name=GRANDPARENT");
assertThat(idValues, contains(pid));
// Search param on extension
myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg=" + orgId.getValue());
myCaptureQueriesListener.logSelectQueries();
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.name=ORGANIZATION");
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.name=PARENT");
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT");
assertThat(idValues, contains(pid));
}
@Test
public void testSearchFetchPageBeyondEnd() {
@ -319,6 +397,38 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
}
@Test
public void testUpdateWithNoBody() throws IOException {
HttpPut httpPost = new HttpPut(ourServerBase + "/Patient/AAA");
try (CloseableHttpResponse status = ourHttpClient.execute(httpPost)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(status.getStatusLine().toString());
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("No body was supplied in request"));
}
}
@Test
public void testCreateWithNoBody() throws IOException {
HttpPost httpPost = new HttpPost(ourServerBase + "/Patient");
try (CloseableHttpResponse status = ourHttpClient.execute(httpPost)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(status.getStatusLine().toString());
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("No body was supplied in request"));
}
}
@Before
public void beforeDisableResultReuse() {
@ -398,7 +508,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@Test
@Ignore
public void test() throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse?_count=50&status=completed&questionnaire=ARIncenterAbsRecord&_lastUpdated=%3E"+UrlUtil.escapeUrlParam("=2018-01-01")+"&context.organization=O3435");
HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse?_count=50&status=completed&questionnaire=ARIncenterAbsRecord&_lastUpdated=%3E" + UrlUtil.escapeUrlParam("=2018-01-01") + "&context.organization=O3435");
ourLog.info("*** MAKING QUERY");
ourHttpClient.execute(get);
System.exit(0);
@ -549,7 +659,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Binary fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute();
assertEquals("1", fromDB.getIdElement().getVersionIdPart());
arr[ 0 ] = 2;
arr[0] = 2;
HttpPut putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart());
putRequest.setEntity(new ByteArrayEntity(arr, ContentType.parse("dansk")));
CloseableHttpResponse resp = ourHttpClient.execute(putRequest);
@ -563,7 +673,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute();
assertEquals("2", fromDB.getIdElement().getVersionIdPart());
arr[ 0 ] = 3;
arr[0] = 3;
fromDB.setContent(arr);
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(fromDB);
putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart());
@ -581,7 +691,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
// Now an update with the wrong ID in the body
arr[ 0 ] = 4;
arr[0] = 4;
binary.setId("");
encoded = myFhirCtx.newJsonParser().encodeResourceToString(binary);
putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart());
@ -637,8 +747,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
public void interceptRequest(IHttpRequest theRequest) {
theRequest.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME);
}
@Override
public void interceptResponse(IHttpResponse theResponse) { // TODO Auto-generated method stu
public void interceptResponse(IHttpResponse theResponse) { // TODO Auto-generated method stu
}
});
@ -1193,7 +1304,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
// String response = "";
StringBuilder b = new StringBuilder();
char[] buf = new char[ 1000 ];
char[] buf = new char[1000];
while (socketInput.read(buf) != -1) {
b.append(buf);
}
@ -5194,7 +5305,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
private String toStr(Date theDate) {
return new InstantDt(theDate).getValueAsString();
}

View File

@ -24,6 +24,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.model.search.PerformanceMessage;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.retry.Retrier;
@ -41,6 +44,7 @@ import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -63,6 +67,9 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
private volatile long myLastRefresh;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Override
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
@ -121,6 +128,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
}
private void populateActiveSearchParams(Map<String, Map<String, RuntimeSearchParam>> theActiveSearchParams) {
Map<String, List<JpaRuntimeSearchParam>> activeUniqueSearchParams = new HashMap<>();
Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> activeParamNamesToUniqueSearchParams = new HashMap<>();
@ -133,8 +141,13 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextResourceNameToEntries : theActiveSearchParams.entrySet()) {
List<JpaRuntimeSearchParam> uniqueSearchParams = activeUniqueSearchParams.computeIfAbsent(nextResourceNameToEntries.getKey(), k -> new ArrayList<>());
Collection<RuntimeSearchParam> nextSearchParamsForResourceName = nextResourceNameToEntries.getValue().values();
ourLog.trace("Resource {} has {} params", nextResourceNameToEntries.getKey(), nextResourceNameToEntries.getValue().size());
for (RuntimeSearchParam nextCandidate : nextSearchParamsForResourceName) {
ourLog.trace("Resource {} has parameter {} with ID {}", nextResourceNameToEntries.getKey(), nextCandidate.getName(), nextCandidate.getId());
if (nextCandidate.getId() != null) {
idToRuntimeSearchParam.put(nextCandidate.getId().toUnqualifiedVersionless().getValue(), nextCandidate);
}
@ -150,6 +163,8 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
}
ourLog.trace("Have {} search params loaded", idToRuntimeSearchParam.size());
Set<String> haveSeen = new HashSet<>();
for (JpaRuntimeSearchParam next : jpaSearchParams) {
if (!haveSeen.add(next.getId().toUnqualifiedVersionless().getValue())) {
@ -164,7 +179,14 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
next.getCompositeOf().add(componentTarget);
paramNames.add(componentTarget.getName());
} else {
ourLog.warn("Search parameter {} refers to unknown component {}", next.getId().toUnqualifiedVersionless().getValue(), nextRef);
String existingParams = idToRuntimeSearchParam
.keySet()
.stream()
.sorted()
.collect(Collectors.joining(", "));
String message = "Search parameter " + next.getId().toUnqualifiedVersionless().getValue() + " refers to unknown component " + nextRef + ", ignoring this parameter (valid values: " + existingParams + ")";
ourLog.warn(message);
myInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_MESSAGE, new PerformanceMessage().setMessage(message));
}
}
@ -182,6 +204,8 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
}
}
ourLog.trace("Have {} unique search params", activeParamNamesToUniqueSearchParams.size());
myActiveUniqueSearchParams = activeUniqueSearchParams;
myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams;
}
@ -225,12 +249,15 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
IBundleProvider allSearchParamsBp = mySearchParamProvider.search(params);
int size = allSearchParamsBp.size();
ourLog.trace("Loaded {} search params from the DB", size);
// Just in case..
if (size >= MAX_MANAGED_PARAM_COUNT) {
ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!");
size = MAX_MANAGED_PARAM_COUNT;
}
int overriddenCount = 0;
List<IBaseResource> allSearchParams = allSearchParamsBp.getResources(0, size);
for (IBaseResource nextResource : allSearchParams) {
SP nextSp = (SP) nextResource;
@ -252,11 +279,14 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
String name = runtimeSp.getName();
if (myModelConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
overriddenCount++;
}
}
}
ourLog.trace("Have overridden {} built-in search parameters", overriddenCount);
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<>();
for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextEntry : searchParams.entrySet()) {
for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) {
@ -323,7 +353,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
int refreshCacheWithRetry() {
Retrier<Integer> refreshCacheRetrier = new Retrier(() -> {
synchronized(BaseSearchParamRegistry.this) {
synchronized (BaseSearchParamRegistry.this) {
return mySearchParamProvider.refreshCache(this, REFRESH_INTERVAL);
}
}, MAX_RETRIES);

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.subscription.module;
/*-
* #%L
* HAPI FHIR Subscription 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.jpa.model.interceptor.api.HookParams;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket;
/*-
* #%L
* HAPI FHIR Subscription 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.jpa.subscription.module.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket;
/*-
* #%L
* HAPI FHIR Subscription 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.jpa.subscription.module.cache.ActiveSubscription;
public class WebsocketValidationResponse {

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -98,18 +99,20 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
}
if (myResourceParameterIndex != -1) {
IBaseResource resource = ((IBaseResource) theParams[myResourceParameterIndex]);
String resourceId = resource.getIdElement().getIdPart();
String urlId = theRequest.getId() != null ? theRequest.getId().getIdPart() : null;
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) {
resource.setId(theRequest.getId());
}
if (resource != null) {
String resourceId = resource.getIdElement().getIdPart();
String urlId = theRequest.getId() != null ? theRequest.getId().getIdPart() : null;
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) {
resource.setId(theRequest.getId());
}
String matchUrl = null;
if (myConditionalUrlIndex != -1) {
matchUrl = (String) theParams[myConditionalUrlIndex];
matchUrl = defaultIfBlank(matchUrl, null);
String matchUrl = null;
if (myConditionalUrlIndex != -1) {
matchUrl = (String) theParams[myConditionalUrlIndex];
matchUrl = defaultIfBlank(matchUrl, null);
}
validateResourceIdAndUrlIdForNonConditionalOperation(resource, resourceId, urlId, matchUrl);
}
validateResourceIdAndUrlIdForNonConditionalOperation(resource, resourceId, urlId, matchUrl);
}
}

View File

@ -139,8 +139,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return provideStructureDefinitionMap(theContext).get(theUrl);
}
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
}
public void flush() {

View File

@ -4,6 +4,7 @@ import java.util.List;
import org.hl7.fhir.dstu2016may.model.CodeSystem;
import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.dstu2016may.model.StructureDefinition;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
@ -33,14 +34,23 @@ public interface IValidationSupport
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by ID
* Fetch a code system by Uri
*
* @param theSystem
* The code system
* @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 theSystem);
CodeSystem fetchCodeSystem(FhirContext theContext, String uri);
/**
* Fetch a valueset by Uri
*
* @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

View File

@ -99,8 +99,13 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return myCodeSystems.get(theSystem);
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return myCodeSystems.get(uri);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return myValueSets.get(uri);
}
@SuppressWarnings("unchecked")

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.dstu2016may.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.dstu2016may.model.CodeSystem;
import org.hl7.fhir.dstu2016may.model.StructureDefinition;
import org.hl7.fhir.dstu2016may.model.ValueSet;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -78,9 +79,9 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
public CodeSystem fetchCodeSystem(FhirContext theCtx, String uri) {
for (IValidationSupport next : myChain) {
CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem);
CodeSystem retVal = next.fetchCodeSystem(theCtx, uri);
if (retVal != null) {
return retVal;
}
@ -88,6 +89,18 @@ public class ValidationSupportChain implements IValidationSupport {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theCtx, String uri) {
for (IValidationSupport next : myChain) {
ValueSet retVal = next.fetchValueSet(theCtx, uri);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (IValidationSupport next : myChain) {

View File

@ -155,8 +155,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return retVal;
}
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
}
public void flush() {

View File

@ -5,6 +5,7 @@ import java.util.List;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -32,15 +33,24 @@ public interface IValidationSupport
@Override
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by ID
*
* @param theSystem
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem);
/**
* 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
* @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

View File

@ -1,7 +1,10 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -10,14 +13,17 @@ import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType;
import org.hl7.fhir.dstu3.model.Patient;
@ -26,6 +32,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -93,6 +100,22 @@ public class ServerExceptionDstu3Test {
}
@Test
public void testPostWithNoBody() throws IOException {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(status.getStatusLine().toString());
ourLog.info(responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("Location").getValue(), containsString("Patient/123"));
}
}
@Test
public void testAuthorize() throws Exception {
@ -125,6 +148,12 @@ public class ServerExceptionDstu3Test {
throw ourException;
}
@Create()
public MethodOutcome create(@ResourceParam Patient thePatient) {
Validate.isTrue(thePatient == null);
return new MethodOutcome().setId(new IdType("Patient/123"));
}
}
@AfterClass

View File

@ -158,8 +158,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return provideStructureDefinitionMap(theContext).get(url);
}
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
}
public void flush() {

View File

@ -6,6 +6,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -32,14 +33,23 @@ public interface IValidationSupport
List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext);
/**
* Fetch a code system by ID
* Fetch a code system by Uri
*
* @param theSystem
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
* @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 theSystem);
CodeSystem fetchCodeSystem(FhirContext theContext, String uri);
/**
* Fetch a valueset by Uri
*
* @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

View File

@ -41,8 +41,13 @@ public class CachingValidationSupport implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return myWrap.fetchCodeSystem(theContext, theSystem);
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return myWrap.fetchCodeSystem(theContext, uri);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return myWrap.fetchValueSet(theContext, uri);
}
@Override

View File

@ -146,11 +146,16 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return myCodeSystems.get(theSystem);
}
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return myCodeSystems.get(uri);
}
@SuppressWarnings("unchecked")
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return myValueSets.get(uri);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
if (theClass.equals(StructureDefinition.class)) {

View File

@ -5,6 +5,7 @@ import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -101,6 +102,17 @@ public class ValidationSupportChain implements IValidationSupport {
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theCtx, String uri) {
for (IValidationSupport next : myChain) {
ValueSet retVal = next.fetchValueSet(theCtx, uri);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (IValidationSupport next : myChain) {

View File

@ -42,8 +42,13 @@ public class CachingValidationSupport implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return myWrap.fetchCodeSystem(theContext, theSystem);
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return myWrap.fetchCodeSystem(theContext, uri);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return myWrap.fetchValueSet(theContext, uri);
}
@Override

View File

@ -146,12 +146,18 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
return new ArrayList<StructureDefinition>(myStructureDefinitions.values());
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return myCodeSystems.get(theSystem);
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) {
return myCodeSystems.get(uri);
}
@SuppressWarnings("unchecked")
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return myValueSets.get(uri);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
if (theClass.equals(StructureDefinition.class)) {

View File

@ -7,6 +7,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
@ -70,9 +71,20 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
public CodeSystem fetchCodeSystem(FhirContext theCtx, String uri) {
for (IValidationSupport next : myChain) {
CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem);
CodeSystem retVal = next.fetchCodeSystem(theCtx, uri);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public ValueSet fetchValueSet(FhirContext theCtx, String uri) {
for (IValidationSupport next : myChain) {
ValueSet retVal = next.fetchValueSet(theCtx, uri);
if (retVal != null) {
return retVal;
}

View File

@ -1464,7 +1464,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.21.0</version>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -1491,7 +1491,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<version>3.0.0-M3</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<runOrder>random</runOrder>
@ -2268,7 +2268,6 @@
<module>hapi-fhir-jpaserver-elasticsearch</module>
<module>hapi-fhir-jpaserver-migrate</module>
<module>restful-server-example</module>
<module>restful-server-example-test</module>
<module>hapi-fhir-testpage-overlay</module>
<module>hapi-fhir-jpaserver-uhnfhirtest</module>
<module>hapi-fhir-client-okhttp</module>

View File

@ -1,125 +0,0 @@
/target/
# Created by https://www.gitignore.io
### Java ###
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
### Vim ###
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Eclipse ###
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.loadpath
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# JDT-specific (Eclipse Java Development Tools)
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse

View File

@ -1,81 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>3.8.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>restful-server-example-test</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR Sample RESTful Server - Tests</name>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,97 +0,0 @@
package ca.uhn.example;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.File;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hamcrest.core.StringContains;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.client.api.IGenericClient;
public class ExampleTest {
private static Integer ourPort;
private static Server ourServer;
private static FhirContext ourCtx;
private static IGenericClient ourClient;
@AfterClass
public static void afterClass() throws Exception {
if (ourServer != null) {
ourServer.stop();
}
System.clearProperty("ca.uhn.fhir.to.TesterConfig_SYSPROP_FORCE_SERVERS");
}
/**
* Tests here have some weird windows inconsistency relating to the path for finding the WAR file. Since this test isn't really important to work multiplatform, we can skip it
*/
public static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}
@Test
public void test01Search() throws Exception {
if (isWindows()) {
return;
}
Bundle results = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).execute();
assertEquals(1, results.getEntry().size());
}
@Test
public void test02Read() throws Exception {
if (isWindows()) {
return;
}
Patient results = ourClient.read(Patient.class, "1");
assertThat(results.getNameFirstRep().getGivenAsSingleString(), StringContains.containsString("PatientOne"));
}
@BeforeClass
public static void beforeClass() throws Exception {
if (isWindows()) {
return;
}
if (ourPort != null) {
return;
}
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
String base = "http://localhost:" + ourPort + "/fhir";
System.setProperty("ca.uhn.fhir.to.TesterConfig_SYSPROP_FORCE_SERVERS", "example , Restful Server Example , " + base);
WebAppContext root = new WebAppContext();
root.setAllowDuplicateFragmentNames(true);
root.setWar(new File("../restful-server-example/target/restful-server-example.war").toURI().toString());
root.setContextPath("/");
root.setAttribute(WebAppContext.BASETEMPDIR, "target/tempextrtact");
root.setParentLoaderPriority(false);
root.setCopyWebInf(true);
root.setCopyWebDir(true);
ourServer.setHandler(root);
ourServer.start();
ourCtx = FhirContext.forDstu2();
ourClient = ourCtx.newRestfulGenericClient(base);
}
}

View File

@ -1,37 +0,0 @@
package ca.uhn.example;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
/**
* Finds and provides a free port to use in unit tests
*/
public class RandomServerPortProvider {
private static List<Integer> ourPorts = new ArrayList<Integer>();
public static int findFreePort() {
ServerSocket server;
try {
server = new ServerSocket(0);
int port = server.getLocalPort();
ourPorts.add(port);
server.close();
Thread.sleep(500);
return port;
} catch (IOException e) {
throw new Error(e);
} catch (InterruptedException e) {
throw new Error(e);
}
}
public static List<Integer> list() {
return ourPorts;
}
}

View File

@ -1,30 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
</pattern>
</encoder>
</appender>
<logger name="org.eclipse" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.apache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.thymeleaf" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<!--
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>
-->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -137,6 +137,15 @@
the resource version is not updated and no new version is created. In this situation,
the update time was modified however. It will no longer be updated.
</action>
<action type="fix">
Performing a PUT or POST against a HAPI FHIR Server with no request body caused an
HTTP 500 to be returned instead of a more appropriate HTTP 400. This has been
corrected.
</action>
<action type="fix" issue="1255">
The fetchValueSet method on IValidationSupport implementation was not visible and could
not be overridden. Thanks to Patrick Werner for the pull reuqest!
</action>
</release>
<release version="3.7.0" date="2019-02-06" description="Gale">
<action type="add">