Throw correct HTTP status when validation fails because of missing ID

This commit is contained in:
James Agnew 2017-02-11 19:22:25 -05:00
parent 53f6effd56
commit 82ac69d86c
18 changed files with 419 additions and 54 deletions

View File

@ -14,7 +14,7 @@ import ca.uhn.fhir.context.FhirContext;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -48,7 +48,7 @@ abstract class BaseParam implements IQueryParameterType {
public Boolean getMissing() {
return myMissing;
}
@Override
public final String getQueryParameterQualifier() {
if (myMissing != null && myMissing.booleanValue()) {
@ -73,8 +73,8 @@ abstract class BaseParam implements IQueryParameterType {
}
/**
* If set to non-null value, indicates that this parameter has been populated
* with a "[name]:missing=true" or "[name]:missing=false" vale instead of a
* If set to non-null value, indicates that this parameter has been populated
* with a "[name]:missing=true" or "[name]:missing=false" vale instead of a
* normal value
*
* @return Returns a reference to <code>this</code> for easier method chaining
@ -97,10 +97,10 @@ abstract class BaseParam implements IQueryParameterType {
throw new InvalidRequestException(msg);
}
}
myMissing = null;
doSetValueAsQueryToken(theContext, theParamName, theQualifier, theValue);
}
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.param;
import static org.apache.commons.lang3.StringUtils.defaultString;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -32,17 +33,26 @@ import ca.uhn.fhir.rest.server.Constants;
public class StringParam extends BaseParam implements IQueryParameterType {
private boolean myContains;
private boolean myExact;
private String myValue;
private boolean myContains;
/**
* Constructor
*/
public StringParam() {
}
/**
* Constructor
*/
public StringParam(String theValue) {
setValue(theValue);
}
/**
* Constructor
*/
public StringParam(String theValue, boolean theExact) {
setValue(theValue);
setExact(theExact);
@ -79,6 +89,29 @@ public class StringParam extends BaseParam implements IQueryParameterType {
myValue = ParameterUtil.unescape(theValue);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof StringParam)) {
return false;
}
StringParam other = (StringParam) obj;
EqualsBuilder eb = new EqualsBuilder();
eb.append(myExact, other.myExact);
eb.append(myContains, other.myContains);
eb.append(myValue, other.myValue);
eb.append(getMissing(), other.getMissing());
return eb.isEquals();
}
public String getValue() {
return myValue;
}
@ -91,6 +124,13 @@ public class StringParam extends BaseParam implements IQueryParameterType {
return defaultString(myValue);
}
/**
* String parameter modifier <code>:contains</code>
*/
public boolean isContains() {
return myContains;
}
public boolean isEmpty() {
return StringUtils.isEmpty(myValue);
}
@ -99,6 +139,18 @@ public class StringParam extends BaseParam implements IQueryParameterType {
return myExact;
}
/**
* String parameter modifier <code>:contains</code>
*/
public StringParam setContains(boolean theContains) {
myContains = theContains;
if (myContains) {
setExact(false);
setMissing(null);
}
return this;
}
public StringParam setExact(boolean theExact) {
myExact = theExact;
if (myExact) {
@ -108,13 +160,6 @@ public class StringParam extends BaseParam implements IQueryParameterType {
return this;
}
/**
* String parameter modifier <code>:contains</code>
*/
public boolean isContains() {
return myContains;
}
public StringParam setValue(String theValue) {
myValue = theValue;
return this;
@ -136,16 +181,4 @@ public class StringParam extends BaseParam implements IQueryParameterType {
return builder.toString();
}
/**
* String parameter modifier <code>:contains</code>
*/
public StringParam setContains(boolean theContains) {
myContains = theContains;
if (myContains) {
setExact(false);
setMissing(null);
}
return this;
}
}

View File

@ -67,6 +67,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transac
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided.
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.cantValidateWithNoResource=No resource supplied for $validate operation (resource is required unless mode is \"delete\")
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithInvalidId=Can not process entity with ID[{0}], this is not a valid FHIR ID
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.rest.param;
import static org.junit.Assert.*;
import org.junit.Test;
public class StringParamTest {
@Test
public void testEquals() {
StringParam input = new StringParam("foo", true);
assertTrue(input.equals(input));
assertFalse(input.equals(null));
assertFalse(input.equals(""));
assertFalse(input.equals(new StringParam("foo", false)));
}
}

View File

@ -11,7 +11,7 @@ import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -335,7 +335,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
continue;
}
}
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
String id = nextId.getIdPart();
if (StringUtils.isBlank(id)) {
@ -677,18 +677,19 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
}
}
protected void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
throw new InternalErrorException("Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
throw new InternalErrorException(
"Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
}
}
if (theRequestDetails.getUserData().get(PROCESSING_SUB_REQUEST) == Boolean.TRUE) {
theRequestDetails.notifyIncomingRequestPreHandled(theOperationType);
}
List<IServerInterceptor> interceptors = getConfig().getInterceptors();
if (interceptors == null) {
return;
@ -779,7 +780,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setHasTags(true);
}
}
ArrayList<ResourceTag> existingTags = new ArrayList<ResourceTag>();
if (theEntity.isHasTags()) {
existingTags.addAll(theEntity.getTags());
@ -937,9 +938,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag
* The tag
* The tag
* @return Returns <code>true</code> if the tag should be removed
*/
@SuppressWarnings("unused")
@ -951,9 +952,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity
* The resource
* The resource
* @param theResource
* The resource being persisted
* The resource being persisted
*/
protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing
@ -1009,9 +1010,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* </p>
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag
* The tag
* The tag
* @return Retturns <code>true</code> if the tag should be removed
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1072,7 +1073,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
}
IParser parser = theEntity.getEncoding().newParser(getContext(theEntity.getFhirVersion()));
R retVal;
try {
@ -1241,7 +1242,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
/*
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the matching
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
* matching
* resource.
*/
if (myConfig.isAllowInlineMatchUrlReferences()) {
@ -1437,7 +1439,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setResourceLinks(links);
theEntity.toString();
} // if thePerformIndexing
theEntity = myEntityManager.merge(theEntity);
@ -1538,9 +1540,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
*
* @param theResource
* The resource that is about to be persisted
* The resource that is about to be persisted
* @param theEntityToSave
* TODO
* TODO
*/
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null;
@ -1747,7 +1749,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
paramMap.add(nextParamName, param);
continue;
}
if (Constants.PARAM_COUNT.equals(nextParamName)) {
if (paramList.size() > 0 && paramList.get(0).size() > 0) {
String intString = paramList.get(0).get(0);
@ -1772,7 +1774,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} else {
RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
if (paramDef == null) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
throw new InvalidRequestException(
"Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
}
IQueryParameterAnd<?> param = MethodUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);

View File

@ -56,6 +56,7 @@ import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.FhirTerser;
@ -133,8 +134,11 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
ValidationResult result;
if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else {
} else if (theResource != null) {
result = validator.validateWithResult(theResource);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
if (result.isSuccessful()) {
@ -160,11 +164,11 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new InvalidRequestException("Resource has an ID - ID must not be populated for a FHIR create");
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new InvalidRequestException("Resource has no ID - ID must be populated for a FHIR update");
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
public interface IResourceIndexedSearchParamCoordsDao extends JpaRepository<ResourceIndexedSearchParamCoords, Long> {
// nothing yet
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
public interface IResourceIndexedSearchParamDateDao extends JpaRepository<ResourceIndexedSearchParamDate, Long> {
// nothing yet
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
public interface IResourceIndexedSearchParamNumberDao extends JpaRepository<ResourceIndexedSearchParamNumber, Long> {
// nothing yet
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
public interface IResourceIndexedSearchParamQuantityDao extends JpaRepository<ResourceIndexedSearchParamQuantity, Long> {
// nothing yet
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
public interface IResourceIndexedSearchParamStringDao extends JpaRepository<ResourceIndexedSearchParamString, Long> {
// nothing yet
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
public interface IResourceIndexedSearchParamTokenDao extends JpaRepository<ResourceIndexedSearchParamToken, Long> {
// nothing yet
}

View File

@ -54,6 +54,7 @@ import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.FhirTerser;
@ -143,7 +144,8 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
if (resourceToValidateById != null) {
result = validator.validateWithResult(resourceToValidateById);
} else {
throw new InvalidRequestException("No resource supplied for $validate operation (resource is required unless mode is \"delete\")");
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
@ -180,11 +182,11 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new InvalidRequestException("Resource has an ID - ID must not be populated for a FHIR create");
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new InvalidRequestException("Resource has no ID - ID must be populated for a FHIR update");
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}

View File

@ -2556,4 +2556,61 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testValidateOnNoId() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse/$validate");
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("No resource supplied for $validate operation"));
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
/**
* From a Skype message from Brian Postlethwaite
*/
@Test
public void testValidateQuestionnaireResponseWithNoIdForCreate() throws Exception {
String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"<div>!-- populated from the rendered HTML below --></div>\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\",\"group\":{\"question\":[{\"linkId\":\"d94b4f57-1ca0-4d65-acba-8bd9a3926c8c\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has a valid Medicare or DVA entitlement card\"},{\"linkId\":\"0cbe66db-ff12-473a-940e-4672fb82de44\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has received a MedsCheck, Diabetes MedsCheck, Home Medicines Review (HMR) otr Restidential Medication Management Review (RMMR) in the past 12 months\"},{\"linkId\":\"35790cfd-2d98-4721-963e-9663e1897a17\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient is living at home in a community setting\"},{\"linkId\":\"3ccc8304-76cd-41ff-9360-2c8755590bae\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has been recently diagnosed with type 3 diabetes (in the last 12 months) AND is unable to gain timely access to existing diabetes education or health services in the community OR \"},{\"linkId\":\"b05f6f09-49ec-40f9-a889-9a3fdff9e0da\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has type 2 diabetes , is less than ideally controlled AND is unable to gain timely access to existing diabetes education or health services in their community \"},{\"linkId\":\"4a777f56-800d-4e0b-a9c3-e929832adb5b\",\"answer\":[{\"valueBoolean\":false,\"group\":[{\"linkId\":\"95bbc904-149e-427f-88a4-7f6c8ab186fa\",\"question\":[{\"linkId\":\"f0acea9e-716c-4fce-b7a2-aad59de9d136\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Patient has had an Acute or Adverse Event\"},{\"linkId\":\"e1629159-6dea-4295-a93e-e7c2829ce180\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Exacerbation of a Chronic Disease or Condition\"},{\"linkId\":\"2ce526fa-edaa-44b3-8d5a-6e97f6379ce8\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"New Diagnosis\"},{\"linkId\":\"9d6ffa9f-0110-418c-9ed0-f04910fda2ed\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Recent hospital admission (<3 months)\"},{\"linkId\":\"d2803ff7-25f7-4c7b-ab92-356c49910478\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Major change to regular medication regime\"},{\"linkId\":\"b34af32d-c69d-4d44-889f-5b6d420a7d08\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Suspected non-adherence to the patient's medication regime \"},{\"linkId\":\"74bad553-c273-41e6-8647-22b860430bc2\",\"answer\":[],\"text\":\"Other\"}]}]}],\"text\":\"The patient has experienced one or more of the following recent significant medical events\"},{\"linkId\":\"ecbf4e5a-d4d1-43eb-9f43-0c0e35fc09c7\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The Pharmacist has obtained patient consent to take part in the MedsCheck Service or Diabetes MedsCheck Service&nbsp; and share information obtained during the services with other nominated members of the patients healthcare team (such as their GP, diabetes educator) if required\"},{\"linkId\":\"8ef66774-43b0-4190-873f-cfbb6e980aa9\",\"answer\":[],\"text\":\"Question\"}]}}}]}";
HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true");
post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("No issues detected"));
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
/**
* From a Skype message from Brian Postlethwaite
*/
@Test
public void testValidateQuestionnaireResponseWithNoIdForUpdate() throws Exception {
String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"update\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"<div>!-- populated from the rendered HTML below --></div>\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\",\"group\":{\"question\":[{\"linkId\":\"d94b4f57-1ca0-4d65-acba-8bd9a3926c8c\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has a valid Medicare or DVA entitlement card\"},{\"linkId\":\"0cbe66db-ff12-473a-940e-4672fb82de44\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has received a MedsCheck, Diabetes MedsCheck, Home Medicines Review (HMR) otr Restidential Medication Management Review (RMMR) in the past 12 months\"},{\"linkId\":\"35790cfd-2d98-4721-963e-9663e1897a17\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient is living at home in a community setting\"},{\"linkId\":\"3ccc8304-76cd-41ff-9360-2c8755590bae\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has been recently diagnosed with type 3 diabetes (in the last 12 months) AND is unable to gain timely access to existing diabetes education or health services in the community OR \"},{\"linkId\":\"b05f6f09-49ec-40f9-a889-9a3fdff9e0da\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has type 2 diabetes , is less than ideally controlled AND is unable to gain timely access to existing diabetes education or health services in their community \"},{\"linkId\":\"4a777f56-800d-4e0b-a9c3-e929832adb5b\",\"answer\":[{\"valueBoolean\":false,\"group\":[{\"linkId\":\"95bbc904-149e-427f-88a4-7f6c8ab186fa\",\"question\":[{\"linkId\":\"f0acea9e-716c-4fce-b7a2-aad59de9d136\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Patient has had an Acute or Adverse Event\"},{\"linkId\":\"e1629159-6dea-4295-a93e-e7c2829ce180\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Exacerbation of a Chronic Disease or Condition\"},{\"linkId\":\"2ce526fa-edaa-44b3-8d5a-6e97f6379ce8\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"New Diagnosis\"},{\"linkId\":\"9d6ffa9f-0110-418c-9ed0-f04910fda2ed\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Recent hospital admission (<3 months)\"},{\"linkId\":\"d2803ff7-25f7-4c7b-ab92-356c49910478\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Major change to regular medication regime\"},{\"linkId\":\"b34af32d-c69d-4d44-889f-5b6d420a7d08\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Suspected non-adherence to the patient's medication regime \"},{\"linkId\":\"74bad553-c273-41e6-8647-22b860430bc2\",\"answer\":[],\"text\":\"Other\"}]}]}],\"text\":\"The patient has experienced one or more of the following recent significant medical events\"},{\"linkId\":\"ecbf4e5a-d4d1-43eb-9f43-0c0e35fc09c7\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The Pharmacist has obtained patient consent to take part in the MedsCheck Service or Diabetes MedsCheck Service&nbsp; and share information obtained during the services with other nominated members of the patients healthcare team (such as their GP, diabetes educator) if required\"},{\"linkId\":\"8ef66774-43b0-4190-873f-cfbb6e980aa9\",\"answer\":[],\"text\":\"Question\"}]}}}]}";
HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true");
post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("Resource has no ID"));
assertEquals(422, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
}

View File

@ -6,10 +6,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
@ -181,5 +183,63 @@ public class ResourceProviderQuestionnaireResponseDstu3Test extends BaseResource
}
@Test
public void testValidateOnNoId() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse/$validate");
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("No resource supplied for $validate operation"));
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
/**
* From a Skype message from Brian Postlethwaite
*/
@Test
public void testValidateQuestionnaireResponseWithNoIdForCreate() throws Exception {
String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">!-- populated from the rendered HTML below --></div>\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\"}}]}";
HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true");
post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
/**
* From a Skype message from Brian Postlethwaite
*/
@Test
public void testValidateQuestionnaireResponseWithNoIdForUpdate() throws Exception {
String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"update\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">!-- populated from the rendered HTML below --></div>\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\"}}]}";
HttpPost post = new HttpPost(ourServerBase + "/QuestionnaireResponse/$validate?_pretty=true");
post.setEntity(new StringEntity(input, ContentType.APPLICATION_JSON));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("Resource has no ID"));
assertEquals(422, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);
}
}
}

View File

@ -0,0 +1,5 @@
[*.java]
charset = utf-8
indent_style = space
indent_size = 2

View File

@ -152,6 +152,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new ConfigurationException(e);
}
v.setShouldCheckForIdPresence(false);
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(true);
v.setRequireResourceId(false);

View File

@ -80,7 +80,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// used during the build process to keep the overall volume of messages down
private boolean suppressLoincSnomedMessages;
public InstanceValidator(IWorkerContext theContext) throws Exception {
private boolean shouldCheckForIdPresence = true;
public InstanceValidator(IWorkerContext theContext) throws Exception {
super();
this.context = theContext;
source = Source.InstanceValidator;
@ -117,6 +119,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
public void setShouldCheckForIdPresence(boolean theShouldCheckForIdPresence) {
shouldCheckForIdPresence = theShouldCheckForIdPresence;
}
private boolean check(String v1, String v2) {
return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
}
@ -1825,7 +1831,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type.equals("Extension"))
checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack);
else if (type.equals("Resource"))
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path) && !isParametersEntry(ei.path)); // if
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path) && !isParametersEntry(ei.path) && shouldCheckForIdPresence); // if
// (str.matches(".*([.,/])work\\1$"))
else {
StructureDefinition p = getProfileForType(type);