From a2f77b23d2422cd2f0e2fdafa6ff51d08713d436 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 23 Nov 2019 16:32:37 +0100 Subject: [PATCH] Fix #1583 - Index local refs in canonical types (#1593) * Fix #1583 - Index local refs in canonical types * Add fix to pre-expansion * Test fix * Rework ID handling * Test fixes * Fix test --- .../java/ca/uhn/fhir/parser/BaseParser.java | 76 ++++++------ .../main/java/ca/uhn/fhir/parser/IParser.java | 13 -- .../java/ca/uhn/fhir/util/BundleUtil.java | 9 +- .../fhir/util/bundle/BundleEntryParts.java | 8 +- .../util/bundle/ModifiableBundleEntry.java | 9 ++ .../client/method/ConditionalParamBinder.java | 2 +- .../jpa/term/IValueSetConceptAccumulator.java | 2 +- .../jpa/term/ValueSetConceptAccumulator.java | 10 +- .../r4/FhirResourceDaoR4SearchNoFtTest.java | 22 ++++ ...urceDaoR4SearchWithLuceneDisabledTest.java | 35 ++++++ .../src/test/resources/david_big_bundle.json | 2 +- .../extractor/BaseSearchParamExtractor.java | 30 +++++ .../server/method/ConditionalParamBinder.java | 2 +- .../server/method/OperationMethodBinding.java | 10 ++ hapi-fhir-structures-dstu3/pom.xml | 2 +- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 29 +++++ .../ca/uhn/fhir/parser/XmlParserR4Test.java | 35 +++++- .../rest/server/BlockingContentR4Test.java | 113 ++++++++++++++++++ .../server/ServerInvalidDefinitionR4Test.java | 74 ++++++++---- .../rest/server/ServerMimetypeR4Test.java | 111 +++++++++-------- .../server/ResourceProviderRule.java | 55 +++++++++ .../utilities/server/RestfulServerRule.java | 4 + pom.xml | 2 +- src/changes/changes.xml | 32 +++++ 24 files changed, 548 insertions(+), 139 deletions(-) create mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java rename hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java => hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java (68%) create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/ResourceProviderRule.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 5603d5faa77..4a2a2919ba6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -22,10 +22,16 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IIdentifiableElement; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.base.Charsets; import org.apache.commons.lang3.StringUtils; @@ -35,7 +41,13 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hl7.fhir.instance.model.api.*; import javax.annotation.Nullable; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; import java.lang.reflect.Modifier; import java.util.*; import java.util.stream.Collectors; @@ -530,11 +542,6 @@ public abstract class BaseParser implements IParser { return tags; } - @Override - public Boolean getOverrideResourceIdWithBundleEntryFullUrl() { - return myOverrideResourceIdWithBundleEntryFullUrl; - } - @Override public List> getPreferTypes() { return myPreferTypes; @@ -705,39 +712,38 @@ public abstract class BaseParser implements IParser { RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal); if ("Bundle".equals(def.getName())) { - BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); - BaseRuntimeElementCompositeDefinition entryDef = (BaseRuntimeElementCompositeDefinition) entryChild.getChildByName("entry"); - List entries = entryChild.getAccessor().getValues(retVal); - if (entries != null) { - for (IBase nextEntry : entries) { - - /** - * If Bundle.entry.fullUrl is populated, set the resource ID to that - */ - // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the - // fullUrl idPart - BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl"); - if (fullUrlChild == null) { - continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2 - } - if (isOverrideResourceIdWithBundleEntryFullUrl()) { - List fullUrl = fullUrlChild.getAccessor().getValues(nextEntry); - if (fullUrl != null && !fullUrl.isEmpty()) { - IPrimitiveType value = (IPrimitiveType) fullUrl.get(0); - if (value.isEmpty() == false) { - List entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry); - if (entryResources != null && entryResources.size() > 0) { - IBaseResource res = (IBaseResource) entryResources.get(0); - String versionId = res.getIdElement().getVersionIdPart(); - res.setId(value.getValueAsString()); - if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) { - res.setId(res.getIdElement().withVersion(versionId)); + if (isOverrideResourceIdWithBundleEntryFullUrl()) { + BundleUtil.processEntries(myContext, (IBaseBundle) retVal, t -> { + String fullUrl = t.getFullUrl(); + if (fullUrl != null) { + IBaseResource resource = t.getResource(); + if (resource != null) { + IIdType resourceId = resource.getIdElement(); + if (isBlank(resourceId.getValue())) { + resourceId.setValue(fullUrl); + } else { + if (fullUrl.startsWith("urn:") && fullUrl.endsWith(":" + resourceId.getIdPart())) { + resourceId.setValue(fullUrl); + } else { + IIdType fullUrlId = myContext.getVersion().newIdType(); + fullUrlId.setValue(fullUrl); + if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { + IIdType newId = fullUrlId; + if (!newId.hasVersionIdPart() && resourceId.hasVersionIdPart()) { + newId = newId.withVersion(resourceId.getVersionIdPart()); + } + resourceId.setValue(newId.getValue()); + } else if (StringUtils.equals(fullUrlId.getIdPart(), resourceId.getIdPart())) { + if (fullUrlId.hasBaseUrl()) { + IIdType newResourceId = resourceId.withServerBase(fullUrlId.getBaseUrl(), resourceId.getResourceType()); + resourceId.setValue(newResourceId.getValue()); + } } } } } } - } + }); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index ffa0eed714a..fa5be1593b9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -92,19 +92,6 @@ public interface IParser { */ Boolean getStripVersionsFromReferences(); - /** - * If set to true (which is the default), the Bundle.entry.fullUrl will override the Bundle.entry.resource's - * resource id if the fullUrl is defined. This behavior happens when parsing the source data into a Bundle object. Set this - * to false if this is not the desired behavior (e.g. the client code wishes to perform additional - * validation checks between the fullUrl and the resource id). - * - * @return Returns the parser instance's configuration setting for overriding resource ids with Bundle.entry.fullUrl when - * parsing the source data into a Bundle object. This method will return null if no value is set, in - * which case the value from the {@link ParserOptions} will be used (default is true) - * @see ParserOptions - */ - Boolean getOverrideResourceIdWithBundleEntryFullUrl(); - /** * Is the parser in "summary mode"? See {@link #setSummaryMode(boolean)} for information * diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java index 3fb0b201dab..896057ebe4a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java @@ -168,6 +168,8 @@ public class BundleUtil { BaseRuntimeElementCompositeDefinition entryChildContentsDef = (BaseRuntimeElementCompositeDefinition) entryChildDef.getChildByName("entry"); + BaseRuntimeChildDefinition fullUrlChildDef = entryChildContentsDef.getChildByName("fullUrl"); + BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource"); BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request"); BaseRuntimeElementCompositeDefinition requestChildContentsDef = (BaseRuntimeElementCompositeDefinition) requestChildDef.getChildByName("request"); @@ -180,6 +182,11 @@ public class BundleUtil { String url = null; RequestTypeEnum requestType = null; String conditionalUrl = null; + String fullUrl = fullUrlChildDef + .getAccessor() + .getFirstValueOrNull(nextEntry) + .map(t->((IPrimitiveType)t).getValueAsString()) + .orElse(null); for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) { resource = (IBaseResource) nextResource; @@ -217,7 +224,7 @@ public class BundleUtil { * order in the original bundle. */ BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef); - ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(requestType, url, resource, conditionalUrl), mutator); + ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(fullUrl, requestType, url, resource, conditionalUrl), mutator); theProcessor.accept(entry); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/BundleEntryParts.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/BundleEntryParts.java index f90b67b3f73..75590b4cd43 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/BundleEntryParts.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/BundleEntryParts.java @@ -28,15 +28,21 @@ public class BundleEntryParts { private final IBaseResource myResource; private final String myUrl; private final String myConditionalUrl; + private final String myFullUrl; - public BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) { + public BundleEntryParts(String theFullUrl, RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) { super(); + myFullUrl = theFullUrl; myRequestType = theRequestType; myUrl = theUrl; myResource = theResource; myConditionalUrl = theConditionalUrl; } + public String getFullUrl() { + return myFullUrl; + } + public RequestTypeEnum getRequestType() { return myRequestType; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/ModifiableBundleEntry.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/ModifiableBundleEntry.java index 85c14bdbacc..85beaf4ae04 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/ModifiableBundleEntry.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/bundle/ModifiableBundleEntry.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.util.bundle; */ import ca.uhn.fhir.context.FhirContext; +import org.hl7.fhir.instance.model.api.IBaseResource; public class ModifiableBundleEntry { private final BundleEntryParts myBundleEntryParts; @@ -39,7 +40,15 @@ public class ModifiableBundleEntry { myBundleEntryMutator.setRequestUrl(theFhirContext, theRequestUrl); } + public String getFullUrl() { + return myBundleEntryParts.getFullUrl(); + } + public String getRequestUrl() { return myBundleEntryParts.getUrl(); } + + public IBaseResource getResource() { + return myBundleEntryParts.getResource(); + } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ConditionalParamBinder.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ConditionalParamBinder.java index 8c18ee5b3d3..b4191e51100 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ConditionalParamBinder.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ConditionalParamBinder.java @@ -44,7 +44,7 @@ class ConditionalParamBinder implements IParameter { @Override public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { if (theOuterCollectionType != null || theInnerCollectionType != null || theParameterType.equals(String.class) == false) { - throw new ConfigurationException("Parameters annotated with @" + ConditionalUrlParam.class.getSimpleName() + " must be of type String, found incorrect parameteter in method \"" + theMethod + "\""); + throw new ConfigurationException("Parameters annotated with @" + ConditionalUrlParam.class.getSimpleName() + " must be of type String, found incorrect parameter in method \"" + theMethod + "\""); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java index ef9f60244ae..0e861db3b4c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IValueSetConceptAccumulator.java @@ -31,7 +31,7 @@ public interface IValueSetConceptAccumulator { void includeConcept(String theSystem, String theCode, String theDisplay); - void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection theDesignations); + void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, @Nullable Collection theDesignations); void excludeConcept(String theSystem, String theCode); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java index 00ca0ef7c5a..b313124f9ce 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetConceptAccumulator.java @@ -34,7 +34,9 @@ import java.util.Collection; import java.util.List; import java.util.Optional; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isAnyBlank; +import static org.apache.commons.lang3.StringUtils.isNoneBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetConceptAccumulator.class); @@ -71,8 +73,10 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator { @Override public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection theDesignations) { TermValueSetConcept concept = saveConcept(theSystem, theCode, theDisplay); - for (TermConceptDesignation designation : theDesignations) { - saveConceptDesignation(concept, designation); + if (theDesignations != null) { + for (TermConceptDesignation designation : theDesignations) { + saveConceptDesignation(concept, designation); + } } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index 1782e1f088c..0f5b28be06d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; @@ -2154,6 +2155,27 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { } + @Test + public void testSearchResourceLinkOnCanonical() { + + Questionnaire q = new Questionnaire(); + q.setId("Q"); + myQuestionnaireDao.update(q); + + QuestionnaireResponse qr = new QuestionnaireResponse(); + qr.setId("QR"); + qr.setQuestionnaire("Questionnaire/Q"); + String qrId = myQuestionnaireResponseDao.update(qr).getId().toUnqualifiedVersionless().getValue(); + + List result = toList(myQuestionnaireResponseDao + .search(new SearchParameterMap().setLoadSynchronous(true).add(QuestionnaireResponse.SP_QUESTIONNAIRE, new ReferenceParam("Questionnaire/Q")))); + assertEquals(1, result.size()); + assertEquals(qrId, result.get(0).getIdElement().toUnqualifiedVersionless().getValue()); + + + } + + @Test public void testSearchResourceLinkWithChain() { Patient patient = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java index 437977e7b22..cbde39ed143 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -10,6 +10,8 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -116,6 +118,8 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { private IResourceReindexingSvc myResourceReindexingSvc; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; + @Autowired + private ITermReadSvc myTermSvc; @Before @Transactional() @@ -190,6 +194,36 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { } } + @Test + public void testExpandValueSet() { + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://fooCS"); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.addConcept().setCode("CODEA"); + cs.addConcept().setCode("CODEB"); + myCodeSystemDao.create(cs); + + ValueSet vs = new ValueSet(); + vs.setUrl("http://fooVS"); + vs.getCompose() + .addInclude() + .setSystem("http://fooCS") + .addConcept(new ValueSet.ConceptReferenceComponent().setCode("CODEA")); + + // Explicit expand + ValueSet outcome = myValueSetDao.expand(vs, null); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + assertEquals("CODEA", outcome.getExpansion().getContains().get(0).getCode()); + + // Deferred expand + IIdType id = myValueSetDao.create(vs).getId().toUnqualifiedVersionless(); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + outcome = myValueSetDao.expand(id, null, mySrd); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + assertEquals("CODEA", outcome.getExpansion().getContains().get(0).getCode()); + } + + @Test public void testSearchByCodeIn() { CodeSystem cs = new CodeSystem(); @@ -207,6 +241,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { .addConcept(new ValueSet.ConceptReferenceComponent().setCode("CODEA")); myValueSetDao.create(vs); + Observation obs = new Observation(); obs.getCode().addCoding().setSystem("http://fooCS").setCode("CODEA"); String obs1id = myObservationDao.create(obs).getId().toUnqualifiedVersionless().getValue(); diff --git a/hapi-fhir-jpaserver-base/src/test/resources/david_big_bundle.json b/hapi-fhir-jpaserver-base/src/test/resources/david_big_bundle.json index 269fbe93218..bd33bf7b60a 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/david_big_bundle.json +++ b/hapi-fhir-jpaserver-base/src/test/resources/david_big_bundle.json @@ -2666,7 +2666,7 @@ "fullUrl": "http://fhirtest.uhn.ca/baseDstu3/Practitioner/A45515", "resource": { "resourceType": "Practitioner", - "id": "45515", + "id": "A45515", "meta": { "versionId": "1", "lastUpdated": "2016-05-07T11:45:53.918-04:00" diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 0ea7ade6d02..1985fc5c006 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -51,6 +51,7 @@ import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.IdType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -151,7 +152,31 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String nextType = toRootTypeName(value); switch (nextType) { + case "uri": case "canonical": + String typeName = toTypeName(value); + + // Canonical has a root type of "uri" + if ("canonical".equals(typeName)) { + IPrimitiveType valuePrimitive = (IPrimitiveType) value; + IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance(); + fakeReference.setReference(valuePrimitive.getValueAsString()); + + /* + * See #1583 + * Technically canonical fields should not allow local references (e.g. + * Questionnaire/123) but it seems reasonable for us to interpret a canonical + * containing a local reference for what it is, and allow people to seaerch + * based on that. + */ + IIdType parsed = fakeReference.getReferenceElement(); + if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) { + PathAndRef ref = new PathAndRef(searchParam.getName(), path, fakeReference); + params.add(ref); + break; + } + } + params.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)"); break; case "reference": @@ -789,6 +814,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return rootParentDefinition.getName(); } + private String toTypeName(IBase nextObject) { + BaseRuntimeElementDefinition elementDefinition = getContext().getElementDefinition(nextObject.getClass()); + return elementDefinition.getName(); + } + @SuppressWarnings("unchecked") private void addDateTimeTypes(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { IPrimitiveType nextBaseDateTime = (IPrimitiveType) theValue; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConditionalParamBinder.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConditionalParamBinder.java index f653bdf81a4..6ac600e5323 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConditionalParamBinder.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConditionalParamBinder.java @@ -47,7 +47,7 @@ class ConditionalParamBinder implements IParameter { public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { if (theOuterCollectionType != null || theInnerCollectionType != null || theParameterType.equals(String.class) == false) { throw new ConfigurationException( - "Parameters annotated with @" + ConditionalUrlParam.class.getSimpleName() + " must be of type String, found incorrect parameteter in method \"" + theMethod + "\""); + "Parameters annotated with @" + ConditionalUrlParam.class.getSimpleName() + " must be of type String, found incorrect parameter in method \"" + theMethod + "\""); } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java index 01886322201..879aa945f99 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java @@ -28,6 +28,8 @@ import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -91,6 +93,14 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { myDescription = null; } + for (Annotation[] nextParamAnnotations : theMethod.getParameterAnnotations()) { + for (Annotation nextParam : nextParamAnnotations) { + if (nextParam instanceof OptionalParam || nextParam instanceof RequiredParam) { + throw new ConfigurationException("Illegal method parameter annotation @" + nextParam.annotationType().getSimpleName() + " on method: " + theMethod.toString()); + } + } + } + if (isBlank(theOperationName)) { throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName() + " but this annotation has no name defined"); diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 7be115787df..e5041555f2c 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -150,7 +150,7 @@ javax.servlet javax.servlet-api - 3.0.1 + 3.1.0 true diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index e9e28dcc3bf..cc5dfa8e9d7 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; @@ -174,6 +175,34 @@ public class JsonParserR4Test extends BaseTest { assertEquals("
Copy © 1999
", p.getText().getDivAsString()); } + @Test + public void testEncodeAndParseBundleWithFullUrlAndResourceIdMismatch() { + + MessageHeader header = new MessageHeader(); + header.setId("1.1.1.1"); + header.setDefinition("Hello"); + + Bundle input = new Bundle(); + input + .addEntry() + .setFullUrl("urn:uuid:0.0.0.0") + .setResource(header); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input); + + ourLog.info("Encoded: {}", encoded); + assertThat(encoded, stringContainsInOrder( + "\"fullUrl\": \"urn:uuid:0.0.0.0\"", + "\"id\": \"1.1.1.1\"" + )); + + input = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); + assertEquals("urn:uuid:0.0.0.0", input.getEntry().get(0).getFullUrl()); + assertEquals("MessageHeader/1.1.1.1", input.getEntry().get(0).getResource().getId()); + + } + + @Test public void testEncodeBinary() { Binary b = new Binary(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index cf97110174f..8ddb449ca18 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -1,8 +1,13 @@ package ca.uhn.fhir.parser; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Narrative; import org.junit.Test; import org.slf4j.Logger; @@ -41,5 +46,33 @@ public class XmlParserR4Test { int idx = encoded.indexOf(sectionText); assertNotEquals(-1, idx); } - + + @Test + public void testEncodeAndParseBundleWithFullUrlAndResourceIdMismatch() { + + MessageHeader header = new MessageHeader(); + header.setId("1.1.1.1"); + header.setDefinition("Hello"); + + Bundle input = new Bundle(); + input + .addEntry() + .setFullUrl("urn:uuid:0.0.0.0") + .setResource(header); + + String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(input); + + ourLog.info("Encoded: {}", encoded); + assertThat(encoded, stringContainsInOrder( + "", + "" + )); + + input = ourCtx.newXmlParser().parseResource(Bundle.class, encoded); + assertEquals("urn:uuid:0.0.0.0", input.getEntry().get(0).getFullUrl()); + assertEquals("MessageHeader/1.1.1.1", input.getEntry().get(0).getResource().getId()); + + } + + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java new file mode 100644 index 00000000000..dd6793ec4df --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/BlockingContentR4Test.java @@ -0,0 +1,113 @@ +package ca.uhn.fhir.rest.server; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; +import ca.uhn.fhir.test.utilities.server.ResourceProviderRule; +import ca.uhn.fhir.test.utilities.server.RestfulServerRule; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Patient; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +public class BlockingContentR4Test { + + private static final Logger ourLog = LoggerFactory.getLogger(BlockingContentR4Test.class); + private static FhirContext ourCtx = FhirContext.forR4(); + @ClassRule + public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx); + @Rule + public ResourceProviderRule myPatientProviderRule = new ResourceProviderRule(ourServerRule, new SystemProvider()); + + @Test + public void testCreateWith100Continue() throws Exception { + Patient patient = new Patient(); + patient.setActive(true); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.TRANSACTION); + input.addEntry().setResource(patient).setFullUrl("Patient/").getRequest().setMethod(Bundle.HTTPVerb.POST); + + RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(true).build(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + builder.setDefaultRequestConfig(config); + try (CloseableHttpClient client = builder.build()) { + + String resourceAsString = ourCtx.newJsonParser().encodeResourceToString(input); + +// InputStream inputStream = new BlockingInputStream(resourceAsString.getBytes(Charsets.UTF_8)); + byte[] bytes = resourceAsString.getBytes(Charsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(bytes); + HttpEntity entity = new InputStreamEntity(inputStream, ContentType.parse("application/fhir+json")){ + @Override + public long getContentLength() { + return bytes.length + 100; + } + }; + + HttpPost post = new HttpPost("http://localhost:" + ourServerRule.getPort() + "/"); + post.setEntity(entity); + try (CloseableHttpResponse resp = client.execute(post)) { + ourLog.info(Arrays.asList(resp.getAllHeaders()).toString().replace(", ", "\n")); + ourLog.info(resp.toString()); + ourLog.info(IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8)); + } + + } + } + + private static class BlockingInputStream extends InputStream { + private final ByteArrayInputStream myWrap; + private int myByteCount = 0; + + public BlockingInputStream(byte[] theBytes) { + myWrap = new ByteArrayInputStream(theBytes); + } + + @Override + public int read() throws IOException { + if (myByteCount++ == 10) { + ourLog.info("About to block..."); + try { + Thread.sleep(30000); + } catch (InterruptedException e) { + ourLog.warn("Interrupted", e); + } + } + return myWrap.read(); + } + } + + + public static class SystemProvider { + + @Transaction + public Bundle transaction(@TransactionParam Bundle theInput) { + return theInput; + } + + } + +} diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java similarity index 68% rename from hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java rename to hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java index 5ccf6ddadb2..e2509542a19 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java @@ -1,30 +1,30 @@ package ca.uhn.fhir.rest.server; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.annotation.Validate; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.util.TestUtil; +import org.hamcrest.core.StringContains; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.junit.AfterClass; +import org.junit.Test; + +import javax.servlet.ServletException; + import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import javax.servlet.ServletException; +public class ServerInvalidDefinitionR4Test { -import org.hamcrest.core.StringContains; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.AfterClass; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.util.TestUtil; - -public class ServerInvalidDefinitionDstu2Test { - - private static FhirContext ourCtx = FhirContext.forDstu2(); - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } + private static FhirContext ourCtx = FhirContext.forR4(); @Test public void testWrongConditionalUrlType() { @@ -38,7 +38,7 @@ public class ServerInvalidDefinitionDstu2Test { } catch (ServletException e) { assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException")); assertThat(e.getCause().toString(), StringContains.containsString( - "Parameters annotated with @ConditionalUrlParam must be of type String, found incorrect parameteter in method \"public ca.uhn.fhir.rest.api.MethodOutcome ca.uhn.fhir.rest.server.ServerInvalidDefinitionDstu2Test$UpdateWithWrongConditionalUrlType.update(ca.uhn.fhir.rest.param.TokenParam,ca.uhn.fhir.model.dstu2.resource.Patient)")); + "Parameters annotated with @ConditionalUrlParam must be of type String, found incorrect parameter in method \"public ca.uhn.fhir.rest.api.MethodOutcome ca.uhn.fhir.rest.server.ServerInvalidDefinitionR4Test$UpdateWithWrongConditionalUrlType.update(ca.uhn.fhir.rest.param.TokenParam,org.hl7.fhir.r4.model.Patient)")); } } @@ -54,7 +54,7 @@ public class ServerInvalidDefinitionDstu2Test { } catch (ServletException e) { assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException")); assertThat(e.getCause().toString(), StringContains - .containsString("Method 'update' is annotated with @ResourceParam but has a type that is not an implemtation of org.hl7.fhir.instance.model.api.IBaseResource or String or byte[]")); + .containsString("Method 'update' is annotated with @ResourceParam but has a type that is not an implemtation of org.hl7.fhir.instance.model.api.IBaseResource or String or byte[]")); } } @@ -88,6 +88,29 @@ public class ServerInvalidDefinitionDstu2Test { } } + @Test + public void testWrongParameterAnnotationOnOperation() { + class MyProvider { + + @Operation(name = "foo") + public MethodOutcome update(@OptionalParam(name = "foo") StringType theFoo) { + return null; + } + + } + + RestfulServer srv = new RestfulServer(ourCtx); + srv.setFhirContext(ourCtx); + srv.registerProvider(new MyProvider()); + + try { + srv.init(); + fail(); + } catch (ServletException e) { + assertThat(e.getCause().toString(), StringContains.containsString("Failure scanning class MyProvider: Illegal method parameter annotation @OptionalParam on method: public ca.uhn.fhir.rest.api.MethodOutcome ca.uhn.fhir.rest.server.ServerInvalidDefinitionR4Test$1MyProvider.update(org.hl7.fhir.r4.model.StringType)")); + } + } + public static class UpdateWithWrongConditionalUrlType implements IResourceProvider { @Override @@ -144,4 +167,9 @@ public class ServerInvalidDefinitionDstu2Test { } + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerMimetypeR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerMimetypeR4Test.java index 7717082ad3a..06fcd1851d8 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerMimetypeR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerMimetypeR4Test.java @@ -1,18 +1,17 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.MyPatientWithExtensions; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -40,18 +39,18 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.client.MyPatientWithExtensions; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class ServerMimetypeR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerMimetypeR4Test.class); @@ -406,45 +405,16 @@ public class ServerMimetypeR4Test { return retVal; } - @AfterClass - public static void afterClassClearContext() throws Exception { - JettyUtil.closeServer(ourServer); - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void beforeClass() throws Exception { - ourServer = new Server(0); - - PatientProvider patientProvider = new PatientProvider(); - - ServletHandler proxyHandler = new ServletHandler(); - ourServlet = new RestfulServer(ourCtx); - - ourServlet.setResourceProviders(patientProvider); - ServletHolder servletHolder = new ServletHolder(ourServlet); - proxyHandler.addServletWithMapping(servletHolder, "/*"); - ourServer.setHandler(proxyHandler); - JettyUtil.startServer(ourServer); - ourPort = JettyUtil.getPortForStartedServer(ourServer); - - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); - HttpClientBuilder builder = HttpClientBuilder.create(); - builder.setConnectionManager(connectionManager); - ourClient = builder.build(); - - } - public static class PatientProvider implements IResourceProvider { @Create() public MethodOutcome create(@ResourceParam Patient theIdParam) { OperationOutcome oo = new OperationOutcome(); oo.addIssue().setDiagnostics(theIdParam.getNameFirstRep().getFamily()); - + theIdParam.setId("1"); theIdParam.getMeta().setVersionId("1"); - + return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(oo).setResource(theIdParam); } @@ -480,4 +450,33 @@ public class ServerMimetypeR4Test { } + @AfterClass + public static void afterClassClearContext() throws Exception { + JettyUtil.closeServer(ourServer); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourServer = new Server(0); + + PatientProvider patientProvider = new PatientProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + ourServlet = new RestfulServer(ourCtx); + + ourServlet.setResourceProviders(patientProvider); + ServletHolder servletHolder = new ServletHolder(ourServlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + JettyUtil.startServer(ourServer); + ourPort = JettyUtil.getPortForStartedServer(ourServer); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + + } + } diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/ResourceProviderRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/ResourceProviderRule.java new file mode 100644 index 00000000000..0b7872fb94b --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/ResourceProviderRule.java @@ -0,0 +1,55 @@ +package ca.uhn.fhir.test.utilities.server; + +/*- + * #%L + * HAPI FHIR Test Utilities + * %% + * 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 org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class ResourceProviderRule implements TestRule { + + private final RestfulServerRule myRestfulServerRule; + private Object myProvider; + + /** + * Constructor + */ + public ResourceProviderRule(RestfulServerRule theRestfulServerRule, Object theProvider) { + myRestfulServerRule = theRestfulServerRule; + myProvider = theProvider; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + myRestfulServerRule.getRestfulServer().registerProvider(myProvider); + try { + base.evaluate(); + } finally { + myRestfulServerRule.getRestfulServer().unregisterProvider(myProvider); + } + } + }; + } + +} diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java index f4bd3ba1c1b..cf77892a3ad 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerRule.java @@ -112,4 +112,8 @@ public class RestfulServerRule implements TestRule { public RestfulServer getRestfulServer() { return myServlet; } + + public int getPort() { + return myPort; + } } diff --git a/pom.xml b/pom.xml index 0ce1bf9bb32..7b85ee59019 100755 --- a/pom.xml +++ b/pom.xml @@ -615,7 +615,7 @@ 2.3.1 2.25.1 - 9.4.14.v20181114 + 9.4.23.v20191118 3.0.2 5.4.6.Final diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8aebda495f1..30df7b31289 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,38 @@ HAPI FHIR Changelog + + + The version of a few dependencies have been bumped to the + latest versions (dependent HAPI modules listed in brackets): + +
  • Jetty (CLI): 9.4.14.v20181114 -> 9.4.23.v20191118
  • + + ]]> +
    + + As of FHIR R4, some fields that were previously of type reference are now of type canonical. + One example is QuestionnaireResponse.questionnaire. Technically this means that this field + should no longer contain a relative reference, but as they are sometimes used that way, HAPI + FHIR will now try to be permissive and will index relative link canonical fields + such as (Questionnaire/123) as though it actually was a local relative link. Thanks to + Dean Atchley for reporting and providing a test case! + + + ValueSet PreCalculation did not successfully expand valuesets when Lucene was not enabled + in the JPA server. This has been corrected. + + + When parsing Bundle resources containing other resources, XML/JSON parsers have an option called + "override resource ID with bundle entry fullUrl". This option previously caused any value + found in Bundle.entry.fullUrl to override any value found in + Bundle.entry.resource.id (meaning that the parsed resource would take its ID from + the fullUrl even if that ID disagreed with the ID found in the resource itself. As of + HAPI FHIR 4.1.0 the value in Bundle.entry.fullUrl will only be used to set the parsed resource + ID if the resource has no ID present. + +
    The version of a few dependencies have been bumped to the