Merge branch 'master' into ja_20200206_multitenancy
This commit is contained in:
commit
6226381595
|
@ -40,10 +40,8 @@
|
|||
</p>
|
||||
</th:block>
|
||||
<p>
|
||||
<b style="color: red;">
|
||||
<span class="glyphicon glyphicon-warning-sign"/>
|
||||
This is not a production server!
|
||||
</b>
|
||||
<b class="text-danger"><span class="glyphicon glyphicon-warning-sign"/></b>
|
||||
<b><span class="text-danger">This is not a production server!</span></b>
|
||||
Do not store any information here that contains personal health information
|
||||
or any other confidential information. This server will be regularly purged
|
||||
and reloaded with fixed test data.
|
||||
|
|
|
@ -40,10 +40,8 @@
|
|||
</p>
|
||||
</th:block>
|
||||
<p>
|
||||
<b style="color: red;">
|
||||
<span class="glyphicon glyphicon-warning-sign"/>
|
||||
This is not a production server!
|
||||
</b>
|
||||
<b class="text-danger"><span class="glyphicon glyphicon-warning-sign"/></b>
|
||||
<b><span class="text-danger">This is not a production server!</span></b>
|
||||
Do not store any information here that contains personal health information
|
||||
or any other confidential information. This server will be regularly purged
|
||||
and reloaded with fixed test data.
|
||||
|
|
|
@ -40,10 +40,8 @@
|
|||
</p>
|
||||
</th:block>
|
||||
<p>
|
||||
<b style="color: red;">
|
||||
<span class="glyphicon glyphicon-warning-sign"/>
|
||||
This is not a production server!
|
||||
</b>
|
||||
<b class="text-danger"><span class="glyphicon glyphicon-warning-sign"/></b>
|
||||
<b><span class="text-danger">This is not a production server!</span></b>
|
||||
Do not store any information here that contains personal health information
|
||||
or any other confidential information. This server will be regularly purged
|
||||
and reloaded with fixed test data.
|
||||
|
|
|
@ -21,9 +21,10 @@ package ca.uhn.fhir.rest.gclient;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
public interface IDeleteTyped extends IClientExecutable<IDeleteTyped, IBaseOperationOutcome> {
|
||||
public interface IDeleteTyped extends IClientExecutable<IDeleteTyped, MethodOutcome> {
|
||||
|
||||
/**
|
||||
* Delete cascade mode - Note that this is a HAPI FHIR specific feature and is not supported on all servers.
|
||||
|
|
|
@ -72,6 +72,7 @@ public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQue
|
|||
* on a single page.
|
||||
*
|
||||
* @deprecated This parameter is badly named, since FHIR calls this parameter "_count" and not "_limit". Use {@link #count(int)} instead (it also sets the _count parameter)
|
||||
* @see #count(int)
|
||||
*/
|
||||
@Deprecated
|
||||
IQuery<Y> limitTo(int theLimitTo);
|
||||
|
|
|
@ -139,6 +139,8 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet
|
|||
|
||||
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
|
||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionId=Unknown partition ID: {0}
|
||||
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionName=Unknown partition name: {0}
|
||||
|
|
|
@ -604,7 +604,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private class DeleteInternal extends BaseSearch<IDeleteTyped, IDeleteWithQueryTyped, IBaseOperationOutcome> implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped {
|
||||
private class DeleteInternal extends BaseSearch<IDeleteTyped, IDeleteWithQueryTyped, MethodOutcome> implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped {
|
||||
|
||||
private boolean myConditional;
|
||||
private IIdType myId;
|
||||
|
@ -613,7 +613,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private DeleteCascadeModeEnum myCascadeMode;
|
||||
|
||||
@Override
|
||||
public IBaseOperationOutcome execute() {
|
||||
public MethodOutcome execute() {
|
||||
|
||||
Map<String, List<String>> additionalParams = new HashMap<>();
|
||||
if (myCascadeMode != null) {
|
||||
|
@ -635,7 +635,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
} else {
|
||||
invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), mySearchUrl, getParamMap());
|
||||
}
|
||||
OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler();
|
||||
|
||||
OutcomeResponseHandler binding = new OutcomeResponseHandler();
|
||||
|
||||
return invoke(additionalParams, binding, invocation);
|
||||
}
|
||||
|
@ -1389,30 +1390,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
}
|
||||
|
||||
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<IBaseOperationOutcome> {
|
||||
|
||||
@Override
|
||||
public IBaseOperationOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
|
||||
throws BaseServerResponseException {
|
||||
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
|
||||
if (respType == null) {
|
||||
return null;
|
||||
}
|
||||
IParser parser = respType.newParser(myContext);
|
||||
IBaseOperationOutcome retVal;
|
||||
try {
|
||||
// TODO: handle if something else than OO comes back
|
||||
retVal = (IBaseOperationOutcome) parser.parseResource(theResponseInputStream);
|
||||
} catch (DataFormatException e) {
|
||||
ourLog.warn("Failed to parse OperationOutcome response", e);
|
||||
return null;
|
||||
}
|
||||
MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, retVal);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
|
||||
private PreferReturnEnum myPrefer;
|
||||
|
||||
|
|
|
@ -236,21 +236,27 @@ public class GenericClientExample {
|
|||
// START SNIPPET: conformance
|
||||
// Retrieve the server's conformance statement and print its
|
||||
// description
|
||||
CapabilityStatement conf = client.capabilities().ofType(CapabilityStatement.class).execute();
|
||||
CapabilityStatement conf = client
|
||||
.capabilities()
|
||||
.ofType(CapabilityStatement.class)
|
||||
.execute();
|
||||
System.out.println(conf.getDescriptionElement().getValue());
|
||||
// END SNIPPET: conformance
|
||||
}
|
||||
{
|
||||
// START SNIPPET: delete
|
||||
IBaseOperationOutcome resp = client.delete().resourceById(new IdType("Patient", "1234")).execute();
|
||||
MethodOutcome response = client
|
||||
.delete()
|
||||
.resourceById(new IdType("Patient", "1234"))
|
||||
.execute();
|
||||
|
||||
// outcome may be null if the server didn't return one
|
||||
if (resp != null) {
|
||||
OperationOutcome outcome = (OperationOutcome) resp;
|
||||
System.out.println(outcome.getIssueFirstRep().getDetails().getCodingFirstRep().getCode());
|
||||
}
|
||||
// END SNIPPET: delete
|
||||
}
|
||||
OperationOutcome outcome = (OperationOutcome) response.getOperationOutcome();
|
||||
if (outcome != null) {
|
||||
System.out.println(outcome.getIssueFirstRep().getDetails().getCodingFirstRep().getCode());
|
||||
}
|
||||
// END SNIPPET: delete
|
||||
}
|
||||
{
|
||||
// START SNIPPET: deleteConditional
|
||||
client.delete()
|
||||
|
@ -356,7 +362,8 @@ public class GenericClientExample {
|
|||
.revInclude(Provenance.INCLUDE_TARGET)
|
||||
.lastUpdated(new DateRangeParam("2011-01-01", null))
|
||||
.sort().ascending(Patient.BIRTHDATE)
|
||||
.sort().descending(Patient.NAME).limitTo(123)
|
||||
.sort().descending(Patient.NAME)
|
||||
.count(123)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
// END SNIPPET: searchAdv
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1791
|
||||
title: The GraphQL Expression parser sometimes fails and reports unhelpful error messages when using search arguments.
|
||||
Thanks to Ibrohim Kholilul Islam for the pull request!
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1806
|
||||
title: The JPA server ElasticSearch provider failed to initialize if username/password credentials were not
|
||||
explicitly provided, meaning it could not run on AWS-supplied ElasticSearch. Thanks to Maciej Kucharek for
|
||||
the pull request!
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1810
|
||||
title: The text styling on the Testpage Overlay homepage has been improved to use native
|
||||
Bootstrap warning colours. Thanks to Joel Schneider for the pull request!
|
|
@ -36,6 +36,23 @@
|
|||
These classes have not changed in terms of functionality, but existing projects may need to adjust some
|
||||
package import statements.
|
||||
"
|
||||
- item:
|
||||
issue: "1804"
|
||||
type: "change"
|
||||
title: "**Breaking Change**:
|
||||
The Generic/Fluent **delete()** operation now returns a [MethodOutcome](/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/api/MethodOutcome.html)
|
||||
object instead of an OperationOutcome. The OperationOutcomoe is still available direcly by querying
|
||||
the MethodOutcome object, but this change makes the delete() method more consistent with
|
||||
other similar methods in the API.
|
||||
"
|
||||
- item:
|
||||
type: "change"
|
||||
title: "**Breaking Change**:
|
||||
Some R4 and R5 structure fields containing a `code` value with a **Required (closed) binding**
|
||||
did not use the java Enum type that was generated for the given field. These have been changed
|
||||
to use the Enum values where possible. This change does not remove any functionality from the model
|
||||
but may require a small amount of re-coding to deal with new setter/getter types on a few fields.
|
||||
"
|
||||
- item:
|
||||
issue: "1807"
|
||||
type: "change"
|
||||
|
|
|
@ -168,7 +168,7 @@ public class AbstractJaxRsResourceProviderDstu3Test {
|
|||
@Test
|
||||
public void testDeletePatient() {
|
||||
when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
||||
final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute();
|
||||
final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute().getOperationOutcome();
|
||||
assertEquals("1", idCaptor.getValue().getIdPart());
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ public class AbstractJaxRsResourceProviderTest {
|
|||
@Test
|
||||
public void testDeletePatient() {
|
||||
when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
||||
final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute();
|
||||
final IBaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute().getOperationOutcome();
|
||||
assertEquals("1", idCaptor.getValue().getIdPart());
|
||||
}
|
||||
|
||||
|
|
|
@ -45,21 +45,42 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.hl7.fhir.utilities.graphql.Argument;
|
||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||
import org.hl7.fhir.utilities.graphql.Value;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.rest.api.Constants.PARAM_FILTER;
|
||||
|
||||
public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implements IGraphQLStorageServices {
|
||||
|
||||
private static final int MAX_SEARCH_SIZE = 500;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(JpaStorageServices.class);
|
||||
|
||||
private IFhirResourceDao<? extends IBaseResource> getDao(String theResourceType) {
|
||||
RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(theResourceType);
|
||||
return myDaoRegistry.getResourceDaoOrNull(typeDef.getImplementingClass());
|
||||
}
|
||||
|
||||
private String graphqlArgumentToSearchParam(String name) {
|
||||
if (name.startsWith("_")) {
|
||||
return name;
|
||||
} else {
|
||||
return name.replaceAll("_", "-");
|
||||
}
|
||||
}
|
||||
|
||||
private String searchParamToGraphqlArgument(String name) {
|
||||
return name.replaceAll("-", "_");
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
@Override
|
||||
public void listResources(Object theAppInfo, String theType, List<Argument> theSearchParams, List<IBaseResource> theMatches) throws FHIRException {
|
||||
|
@ -70,9 +91,25 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
|
|||
SearchParameterMap params = new SearchParameterMap();
|
||||
params.setLoadSynchronousUpTo(MAX_SEARCH_SIZE);
|
||||
|
||||
Map<String, RuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveSearchParams(typeDef.getName());
|
||||
|
||||
for (Argument nextArgument : theSearchParams) {
|
||||
|
||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getSearchParamByName(typeDef, nextArgument.getName());
|
||||
if (nextArgument.getName().equals(PARAM_FILTER)) {
|
||||
String value = nextArgument.getValues().get(0).getValue();
|
||||
params.add(PARAM_FILTER, new StringParam(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
String searchParamName = graphqlArgumentToSearchParam(nextArgument.getName());
|
||||
RuntimeSearchParam searchParam = searchParams.get(searchParamName);
|
||||
if (searchParam == null) {
|
||||
Set<String> graphqlArguments = searchParams.keySet().stream()
|
||||
.map(this::searchParamToGraphqlArgument)
|
||||
.collect(Collectors.toSet());
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(JpaStorageServices.class, "invalidGraphqlArgument", nextArgument.getName(), new TreeSet<>(graphqlArguments));
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
|
||||
for (Value nextValue : nextArgument.getValues()) {
|
||||
String value = nextValue.getValue();
|
||||
|
@ -108,7 +145,7 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
|
|||
break;
|
||||
}
|
||||
|
||||
params.add(nextArgument.getName(), param);
|
||||
params.add(searchParamName, param);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,10 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictOutcome;
|
|||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -45,6 +47,9 @@ import org.hl7.fhir.r4.model.OperationOutcome;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.constraints.Null;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
@ -92,7 +97,12 @@ public class CascadingDeleteInterceptor {
|
|||
public DeleteConflictOutcome handleDeleteConflicts(DeleteConflictList theConflictList, RequestDetails theRequest) {
|
||||
ourLog.debug("Have delete conflicts: {}", theConflictList);
|
||||
|
||||
if (!shouldCascade(theRequest)) {
|
||||
if (shouldCascade(theRequest) == DeleteCascadeModeEnum.NONE) {
|
||||
|
||||
// Add a message to the response
|
||||
String message = theRequest.getFhirContext().getLocalizer().getMessage(CascadingDeleteInterceptor.class, "noParam");
|
||||
theRequest.getUserData().put(CASCADED_DELETES_FAILED_KEY, message);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -180,28 +190,12 @@ public class CascadingDeleteInterceptor {
|
|||
/**
|
||||
* Subclasses may override
|
||||
*
|
||||
* @param theRequest The REST request
|
||||
* @param theRequest The REST request (may be null)
|
||||
* @return Returns true if cascading delete should be allowed
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected boolean shouldCascade(RequestDetails theRequest) {
|
||||
if (theRequest != null) {
|
||||
String[] cascadeParameters = theRequest.getParameters().get(Constants.PARAMETER_CASCADE_DELETE);
|
||||
if (cascadeParameters != null && Arrays.asList(cascadeParameters).contains(Constants.CASCADE_DELETE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String cascadeHeader = theRequest.getHeader(Constants.HEADER_CASCADE);
|
||||
if (Constants.CASCADE_DELETE.equals(cascadeHeader)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add a message to the response
|
||||
String message = theRequest.getFhirContext().getLocalizer().getMessage(CascadingDeleteInterceptor.class, "noParam");
|
||||
theRequest.getUserData().put(CASCADED_DELETES_FAILED_KEY, message);
|
||||
}
|
||||
|
||||
return false;
|
||||
@Nonnull
|
||||
protected DeleteCascadeModeEnum shouldCascade(@Nullable RequestDetails theRequest) {
|
||||
return RestfulServerUtils.extractDeleteCascadeParameter(theRequest);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.search.elastic;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.search.cfg.Environment;
|
||||
import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment;
|
||||
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
|
||||
|
@ -63,8 +64,12 @@ public class ElasticsearchHibernatePropertiesBuilder {
|
|||
theProperties.put("hibernate.search." + ElasticsearchEnvironment.ANALYSIS_DEFINITION_PROVIDER, ElasticsearchMappingProvider.class.getName());
|
||||
|
||||
theProperties.put("hibernate.search.default.elasticsearch.host", myRestUrl);
|
||||
theProperties.put("hibernate.search.default.elasticsearch.username", myUsername);
|
||||
theProperties.put("hibernate.search.default.elasticsearch.password", myPassword);
|
||||
if (StringUtils.isNotBlank(myUsername)) {
|
||||
theProperties.put("hibernate.search.default.elasticsearch.username", myUsername);
|
||||
}
|
||||
if (StringUtils.isNotBlank(myPassword)) {
|
||||
theProperties.put("hibernate.search.default.elasticsearch.password", myPassword);
|
||||
}
|
||||
|
||||
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_SCHEMA_MANAGEMENT_STRATEGY, myIndexSchemaManagementStrategy.getExternalName());
|
||||
theProperties.put("hibernate.search.default." + ElasticsearchEnvironment.INDEX_MANAGEMENT_WAIT_TIMEOUT, Long.toString(myIndexManagementWaitTimeoutMillis));
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package ca.uhn.fhir.jpa.graphql;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Appointment;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.utilities.graphql.Argument;
|
||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||
import org.hl7.fhir.utilities.graphql.StringValue;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ContextConfiguration(classes = {TestR4Config.class})
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class JpaStorageServicesTest extends BaseJpaR4Test {
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setFilterParameterEnabled(new DaoConfig().isFilterParameterEnabled());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myDaoConfig.setFilterParameterEnabled(true);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IGraphQLStorageServices mySvc;
|
||||
|
||||
private String createSomeAppointment() {
|
||||
CodeableConcept someCodeableConcept = new CodeableConcept(new Coding("TEST_SYSTEM", "TEST_CODE", "TEST_DISPLAY"));
|
||||
Appointment someAppointment = new Appointment();
|
||||
someAppointment.setAppointmentType(someCodeableConcept);
|
||||
return myAppointmentDao.create(someAppointment).getId().getIdPart();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListResourcesGraphqlArgumentConversion() {
|
||||
String appointmentId = createSomeAppointment();
|
||||
|
||||
Argument argument = new Argument("appointment_type", new StringValue("TEST_CODE"));
|
||||
|
||||
List<IBaseResource> result = new ArrayList<>();
|
||||
mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result);
|
||||
|
||||
Assert.assertFalse(result.isEmpty());
|
||||
Assert.assertTrue(result.stream().anyMatch((it) -> it.getIdElement().getIdPart().equals(appointmentId)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListResourceGraphqlFilterArgument() {
|
||||
String appointmentId = createSomeAppointment();
|
||||
|
||||
Argument argument = new Argument("_filter", new StringValue("appointment-type eq TEST_CODE"));
|
||||
|
||||
List<IBaseResource> result = new ArrayList<>();
|
||||
mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result);
|
||||
|
||||
Assert.assertFalse(result.isEmpty());
|
||||
Assert.assertTrue(result.stream().anyMatch((it) -> it.getIdElement().getIdPart().equals(appointmentId)));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidRequestException.class)
|
||||
public void testListResourceGraphqlInvalidException() {
|
||||
Argument argument = new Argument("test", new StringValue("some test value"));
|
||||
|
||||
List<IBaseResource> result = new ArrayList<>();
|
||||
mySvc.listResources(mySrd, "Appointment", Collections.singletonList(argument), result);
|
||||
}
|
||||
}
|
|
@ -814,15 +814,13 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
|
||||
//@formatter:off
|
||||
IBaseOperationOutcome response = ourClient
|
||||
MethodOutcome response = ourClient
|
||||
.delete()
|
||||
.resourceConditionalByType(Patient.class)
|
||||
.where(Patient.IDENTIFIER.exactly().code(methodName))
|
||||
.execute();
|
||||
//@formatter:on
|
||||
|
||||
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response);
|
||||
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response.getOperationOutcome());
|
||||
ourLog.info(encoded);
|
||||
assertThat(encoded, containsString(
|
||||
"<issue><severity value=\"information\"/><code value=\"informational\"/><diagnostics value=\"Successfully deleted 2 resource(s) in "));
|
||||
|
@ -1028,8 +1026,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
p.addName().setFamily("FAM");
|
||||
IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
IBaseOperationOutcome resp = ourClient.delete().resourceById(id).execute();
|
||||
OperationOutcome oo = (OperationOutcome) resp;
|
||||
MethodOutcome resp = ourClient.delete().resourceById(id).execute();
|
||||
OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome();
|
||||
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in "));
|
||||
}
|
||||
|
||||
|
|
|
@ -1257,15 +1257,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
|
||||
//@formatter:off
|
||||
IBaseOperationOutcome response = ourClient
|
||||
MethodOutcome response = ourClient
|
||||
.delete()
|
||||
.resourceConditionalByType(Patient.class)
|
||||
.where(Patient.IDENTIFIER.exactly().code(methodName))
|
||||
.execute();
|
||||
//@formatter:on
|
||||
|
||||
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response);
|
||||
String encoded = myFhirCtx.newXmlParser().encodeResourceToString(response.getOperationOutcome());
|
||||
ourLog.info(encoded);
|
||||
assertThat(encoded, containsString(
|
||||
"<issue><severity value=\"information\"/><code value=\"informational\"/><diagnostics value=\"Successfully deleted 2 resource(s) in "));
|
||||
|
@ -1478,8 +1476,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
p.addName().setFamily("FAM");
|
||||
IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
IBaseOperationOutcome resp = ourClient.delete().resourceById(id).execute();
|
||||
OperationOutcome oo = (OperationOutcome) resp;
|
||||
MethodOutcome resp = ourClient.delete().resourceById(id).execute();
|
||||
OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome();
|
||||
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in "));
|
||||
}
|
||||
|
||||
|
|
|
@ -40,10 +40,8 @@
|
|||
</p>
|
||||
</th:block>
|
||||
<p>
|
||||
<b style="color: red;">
|
||||
<span class="glyphicon glyphicon-warning-sign"/>
|
||||
This is not a production server!
|
||||
</b>
|
||||
<b class="text-danger"><span class="glyphicon glyphicon-warning-sign"/></b>
|
||||
<b><span class="text-danger">This is not a production server!</span></b>
|
||||
Do not store any information here that contains personal health information
|
||||
or any other confidential information. This server will be regularly purged
|
||||
and reloaded with fixed test data.
|
||||
|
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
|
|||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.PreferHeader;
|
||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||
|
@ -78,6 +79,7 @@ public class RestfulServerUtils {
|
|||
|
||||
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<>(Arrays.asList("*.text", "*.id", "*.meta", "*.(mandatory)"));
|
||||
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<>());
|
||||
private static EnumSet<RestOperationTypeEnum> ourOperationsWhichAllowPreferHeader = EnumSet.of(RestOperationTypeEnum.CREATE, RestOperationTypeEnum.UPDATE, RestOperationTypeEnum.PATCH);
|
||||
|
||||
private enum NarrativeModeEnum {
|
||||
NORMAL, ONLY, SUPPRESS;
|
||||
|
@ -696,59 +698,55 @@ public class RestfulServerUtils {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private static EnumSet<RestOperationTypeEnum> ourOperationsWhichAllowPreferHeader = EnumSet.of(RestOperationTypeEnum.CREATE, RestOperationTypeEnum.UPDATE, RestOperationTypeEnum.PATCH);
|
||||
|
||||
public static boolean respectPreferHeader(RestOperationTypeEnum theRestOperationType) {
|
||||
return ourOperationsWhichAllowPreferHeader.contains(theRestOperationType);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PreferHeader parsePreferHeader(IRestfulServer<?> theServer, String theValue) {
|
||||
PreferHeader retVal = new PreferHeader();
|
||||
|
||||
if (isNotBlank(theValue)) {
|
||||
StringTokenizer tok = new StringTokenizer(theValue, ";");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String next = trim(tok.nextToken());
|
||||
int eqIndex = next.indexOf('=');
|
||||
|
||||
String key;
|
||||
String value;
|
||||
if (eqIndex == -1 || eqIndex >= next.length() - 2) {
|
||||
key = next;
|
||||
value = "";
|
||||
} else {
|
||||
key = next.substring(0, eqIndex).trim();
|
||||
value = next.substring(eqIndex + 1).trim();
|
||||
}
|
||||
|
||||
if (key.equals(Constants.HEADER_PREFER_RETURN)) {
|
||||
|
||||
if (value.length() < 2) {
|
||||
continue;
|
||||
}
|
||||
if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) {
|
||||
value = value.substring(1, value.length() - 1);
|
||||
}
|
||||
|
||||
retVal.setReturn(PreferReturnEnum.fromHeaderValue(value));
|
||||
|
||||
} else if (key.equals(Constants.HEADER_PREFER_RESPOND_ASYNC)) {
|
||||
|
||||
retVal.setRespondAsync(true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
public static PreferHeader parsePreferHeader(IRestfulServer<?> theServer, String theValue) {
|
||||
PreferHeader retVal = new PreferHeader();
|
||||
|
||||
if (isNotBlank(theValue)) {
|
||||
StringTokenizer tok = new StringTokenizer(theValue, ";");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String next = trim(tok.nextToken());
|
||||
int eqIndex = next.indexOf('=');
|
||||
|
||||
String key;
|
||||
String value;
|
||||
if (eqIndex == -1 || eqIndex >= next.length() - 2) {
|
||||
key = next;
|
||||
value = "";
|
||||
} else {
|
||||
key = next.substring(0, eqIndex).trim();
|
||||
value = next.substring(eqIndex + 1).trim();
|
||||
}
|
||||
|
||||
if (key.equals(Constants.HEADER_PREFER_RETURN)) {
|
||||
|
||||
if (value.length() < 2) {
|
||||
continue;
|
||||
}
|
||||
if ('"' == value.charAt(0) && '"' == value.charAt(value.length() - 1)) {
|
||||
value = value.substring(1, value.length() - 1);
|
||||
}
|
||||
|
||||
retVal.setReturn(PreferReturnEnum.fromHeaderValue(value));
|
||||
|
||||
} else if (key.equals(Constants.HEADER_PREFER_RESPOND_ASYNC)) {
|
||||
|
||||
retVal.setRespondAsync(true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retVal.getReturn() == null && theServer != null && theServer.getDefaultPreferReturn() != null) {
|
||||
retVal.setReturn(theServer.getDefaultPreferReturn());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) {
|
||||
|
@ -772,12 +770,12 @@ public class RestfulServerUtils {
|
|||
}
|
||||
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int stausCode, boolean theAddContentLocationHeader,
|
||||
boolean respondGzip, RequestDetails theRequestDetails) throws IOException {
|
||||
boolean respondGzip, RequestDetails theRequestDetails) throws IOException {
|
||||
return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null);
|
||||
}
|
||||
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStatusCode, String theStatusMessage,
|
||||
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
||||
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
||||
throws IOException {
|
||||
IRestfulResponse response = theRequestDetails.getResponse();
|
||||
|
||||
|
@ -954,5 +952,22 @@ public class RestfulServerUtils {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static DeleteCascadeModeEnum extractDeleteCascadeParameter(RequestDetails theRequest) {
|
||||
if (theRequest != null) {
|
||||
String[] cascadeParameters = theRequest.getParameters().get(Constants.PARAMETER_CASCADE_DELETE);
|
||||
if (cascadeParameters != null && Arrays.asList(cascadeParameters).contains(Constants.CASCADE_DELETE)) {
|
||||
return DeleteCascadeModeEnum.DELETE;
|
||||
}
|
||||
|
||||
String cascadeHeader = theRequest.getHeader(Constants.HEADER_CASCADE);
|
||||
if (Constants.CASCADE_DELETE.equals(cascadeHeader)) {
|
||||
return DeleteCascadeModeEnum.DELETE;
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteCascadeModeEnum.NONE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -377,7 +377,7 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
|
|||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
IBaseOperationOutcome outcome;
|
||||
MethodOutcome outcome;
|
||||
|
||||
// Regular delete
|
||||
outcome = client
|
||||
|
@ -1629,7 +1629,6 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
|
|||
idx++;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testSearchByQuantity() throws Exception {
|
||||
ArgumentCaptor<HttpUriRequest> capt = prepareClientForSearchResponse();
|
||||
|
|
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
|
||||
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
||||
import ca.uhn.fhir.rest.client.impl.BaseClient;
|
||||
import ca.uhn.fhir.rest.client.impl.GenericClient;
|
||||
|
@ -386,23 +387,81 @@ public class GenericClientTest {
|
|||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123")
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue").execute();
|
||||
MethodOutcome outcome = client
|
||||
.delete()
|
||||
.resourceById("Patient", "123")
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue")
|
||||
.execute();
|
||||
|
||||
oo = (OperationOutcome) outcome.getOperationOutcome();
|
||||
assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString());
|
||||
assertEquals("DELETE", capt.getValue().getMethod());
|
||||
Assert.assertEquals("testDelete01", outcome.getIssueFirstRep().getLocation().get(0).getValue());
|
||||
Assert.assertEquals("testDelete01", oo.getIssueFirstRep().getLocation().get(0).getValue());
|
||||
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteInvalidResponse() throws Exception {
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().addLocation("testDelete01");
|
||||
String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo);
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
|
||||
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), StandardCharsets.UTF_8));
|
||||
outcome = (OperationOutcome) client.delete().resourceById(new IdType("Location", "123", "456")).prettyPrint().encodedJson().execute();
|
||||
|
||||
assertEquals("http://example.com/fhir/Location/123?_pretty=true", capt.getAllValues().get(1).getURI().toString());
|
||||
assertEquals("DELETE", capt.getValue().getMethod());
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Assert.assertEquals(null, outcome);
|
||||
// Try with invalid response
|
||||
try {
|
||||
client
|
||||
.delete()
|
||||
.resourceById(new IdType("Location", "123", "456"))
|
||||
.prettyPrint()
|
||||
.encodedJson()
|
||||
.execute();
|
||||
} catch (FhirClientConnectionException e) {
|
||||
assertEquals(0, e.getStatusCode());
|
||||
assertThat(e.getMessage(), containsString("Failed to parse response from server when performing DELETE to URL"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteNoResponse() throws Exception {
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().addLocation("testDelete01");
|
||||
String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo);
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
|
||||
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ooStr), StandardCharsets.UTF_8));
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
MethodOutcome outcome = client
|
||||
.delete()
|
||||
.resourceById("Patient", "123")
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue")
|
||||
.execute();
|
||||
|
||||
oo = (OperationOutcome) outcome.getOperationOutcome();
|
||||
assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString());
|
||||
assertEquals("DELETE", capt.getValue().getMethod());
|
||||
Assert.assertEquals("testDelete01", oo.getIssueFirstRep().getLocation().get(0).getValue());
|
||||
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHistory() throws Exception {
|
||||
|
@ -413,12 +472,8 @@ public class GenericClientTest {
|
|||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8);
|
||||
}
|
||||
});
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(t ->
|
||||
new ReaderInputStream(new StringReader(msg), StandardCharsets.UTF_8));
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
|
@ -428,7 +483,7 @@ public class GenericClientTest {
|
|||
response = client
|
||||
.history()
|
||||
.onServer()
|
||||
.andReturnBundle(Bundle.class)
|
||||
.returnBundle(Bundle.class)
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue")
|
||||
.execute();
|
||||
assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString());
|
||||
|
@ -439,7 +494,7 @@ public class GenericClientTest {
|
|||
response = client
|
||||
.history()
|
||||
.onType(Patient.class)
|
||||
.andReturnBundle(Bundle.class)
|
||||
.returnBundle(Bundle.class)
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue1")
|
||||
.withAdditionalHeader("myHeaderName", "myHeaderValue2")
|
||||
.execute();
|
||||
|
|
Loading…
Reference in New Issue