Add serialization support and fix build regression
This commit is contained in:
parent
e8730b6e47
commit
56a4c1b04b
|
@ -20,17 +20,31 @@ package ca.uhn.fhir.model.api;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public abstract class BasePrimitive<T> extends BaseIdentifiableElement implements IPrimitiveDatatype<T> {
|
||||
public abstract class BasePrimitive<T> extends BaseIdentifiableElement implements IPrimitiveDatatype<T>, Externalizable {
|
||||
|
||||
private T myCoercedValue;
|
||||
private String myStringValue;
|
||||
|
||||
/**
|
||||
* Subclasses must override to convert a "coerced" value into an encoded one.
|
||||
*
|
||||
* @param theValue
|
||||
* Will not be null
|
||||
* @return May return null if the value does not correspond to anything
|
||||
*/
|
||||
protected abstract String encode(T theValue);
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theObj) {
|
||||
if (theObj == null) {
|
||||
|
@ -67,6 +81,21 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
|
|||
return super.isBaseEmpty() && getValue() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must override to convert an encoded representation of this datatype into a "coerced" one
|
||||
*
|
||||
* @param theValue
|
||||
* Will not be null
|
||||
* @return May return null if the value does not correspond to anything
|
||||
*/
|
||||
protected abstract T parse(String theValue);
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput theIn) throws IOException, ClassNotFoundException {
|
||||
String object = (String) theIn.readObject();
|
||||
setValueAsString(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPrimitiveType<T> setValue(T theValue) throws DataFormatException {
|
||||
myCoercedValue = theValue;
|
||||
|
@ -74,15 +103,6 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
|
|||
return this;
|
||||
}
|
||||
|
||||
protected void updateStringValue() {
|
||||
if (myCoercedValue == null) {
|
||||
myStringValue = null;
|
||||
} else {
|
||||
// NB this might be null
|
||||
myStringValue = encode(myCoercedValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAsString(String theValue) throws DataFormatException {
|
||||
if (theValue == null) {
|
||||
|
@ -94,26 +114,22 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
|
|||
myStringValue = theValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must override to convert an encoded representation of this datatype into a "coerced" one
|
||||
*
|
||||
* @param theValue
|
||||
* Will not be null
|
||||
* @return May return null if the value does not correspond to anything
|
||||
*/
|
||||
protected abstract T parse(String theValue);
|
||||
|
||||
/**
|
||||
* Subclasses must override to convert a "coerced" value into an encoded one.
|
||||
*
|
||||
* @param theValue
|
||||
* Will not be null
|
||||
* @return May return null if the value does not correspond to anything
|
||||
*/
|
||||
protected abstract String encode(T theValue);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[" + getValueAsString() + "]";
|
||||
}
|
||||
|
||||
protected void updateStringValue() {
|
||||
if (myCoercedValue == null) {
|
||||
myStringValue = null;
|
||||
} else {
|
||||
// NB this might be null
|
||||
myStringValue = encode(myCoercedValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput theOut) throws IOException {
|
||||
theOut.writeObject(getValueAsString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.model.api;
|
|||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -61,7 +62,9 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|||
* define their own keys for storage in resource metadata if needed.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class ResourceMetadataKeyEnum<T> {
|
||||
public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* If present and populated with a date/time (as an instance of {@link InstantDt}), this value is an indication that the resource is in the deleted state. This key is only used in a limited number
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.model.primitive;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
|
||||
|
@ -28,16 +30,27 @@ public class BoundCodeDt<T extends Enum<?>> extends CodeDt {
|
|||
|
||||
private IValueSetEnumBinder<T> myBinder;
|
||||
|
||||
/**
|
||||
* @deprecated This constructor is provided only for serialization support. Do not call it directly!
|
||||
*/
|
||||
@Deprecated
|
||||
public BoundCodeDt() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
public BoundCodeDt(IValueSetEnumBinder<T> theBinder) {
|
||||
Validate.notNull(theBinder, "theBinder must not be null");
|
||||
myBinder = theBinder;
|
||||
}
|
||||
|
||||
public BoundCodeDt(IValueSetEnumBinder<T> theBinder, T theValue) {
|
||||
Validate.notNull(theBinder, "theBinder must not be null");
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValue);
|
||||
}
|
||||
|
||||
public void setValueAsEnum(T theValue) {
|
||||
Validate.notNull(myBinder, "This object does not have a binder. Constructor BoundCodeDt() should not be called!");
|
||||
if (theValue==null) {
|
||||
setValue(null);
|
||||
} else {
|
||||
|
@ -46,6 +59,7 @@ public class BoundCodeDt<T extends Enum<?>> extends CodeDt {
|
|||
}
|
||||
|
||||
public T getValueAsEnum() {
|
||||
Validate.notNull(myBinder, "This object does not have a binder. Constructor BoundCodeDt() should not be called!");
|
||||
T retVal = myBinder.fromCodeString(getValue());
|
||||
if (retVal == null) {
|
||||
// TODO: throw special exception type?
|
||||
|
|
|
@ -41,6 +41,8 @@ import ca.uhn.fhir.util.XmlUtil;
|
|||
@DatatypeDef(name = "xhtml")
|
||||
public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
|
|
@ -1554,7 +1554,7 @@ class ParserState<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (child.getMax() < 2 && !myParsedNonRepeatableNames.add(theChildName)) {
|
||||
if ((child.getMax() == 0 || child.getMax() == 1) && !myParsedNonRepeatableNames.add(theChildName)) {
|
||||
myErrorHandler.unexpectedRepeatingElement(null, theChildName);
|
||||
push(new SwallowChildrenWholeState(getPreResourceState()));
|
||||
return;
|
||||
|
|
|
@ -24,7 +24,9 @@ import net.sourceforge.cobertura.CoverageIgnore;
|
|||
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 422 Unprocessable Entity</b> response, which means that a resource was rejected by the server because it "violated applicable FHIR profiles or server business rules".
|
||||
|
@ -56,11 +58,21 @@ public class UnprocessableEntityException extends BaseServerResponseException {
|
|||
|
||||
/**
|
||||
* Constructor which accepts an {@link IBaseOperationOutcome} resource which will be supplied in the response
|
||||
*
|
||||
* @deprecated Use constructor with FhirContext argument
|
||||
*/
|
||||
@Deprecated
|
||||
public UnprocessableEntityException(IBaseOperationOutcome theOperationOutcome) {
|
||||
super(STATUS_CODE, DEFAULT_MESSAGE, theOperationOutcome);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which accepts an {@link IBaseOperationOutcome} resource which will be supplied in the response
|
||||
*/
|
||||
public UnprocessableEntityException(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome) {
|
||||
super(STATUS_CODE, OperationOutcomeUtil.getFirstIssueDetails(theCtx, theOperationOutcome), theOperationOutcome);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which accepts a String describing the issue. This string will be translated into an {@link IBaseOperationOutcome} resource which will be supplied in the response.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.validation;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2015 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.validation;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2015 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.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.hl7.fhir.instance.model.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -25,7 +27,7 @@ package org.hl7.fhir.instance.model.api;
|
|||
* structure of some kind. It is provided mostly to simplify convergence
|
||||
* between the HL7.org structures and the HAPI ones.
|
||||
*/
|
||||
public interface IBase {
|
||||
public interface IBase extends Serializable {
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2015 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.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -41,7 +61,7 @@ public class FhirResourceDaoQuestionnaireAnswersDstu2 extends FhirResourceDaoDst
|
|||
ValidationResult result = val.validateWithResult(myRefImplCtx.newJsonParser().parseResource(getContext().newJsonParser().encodeResourceToString(qa)));
|
||||
if (!result.isSuccessful()) {
|
||||
IBaseOperationOutcome oo = getContext().newJsonParser().parseResource(OperationOutcome.class, myRefImplCtx.newJsonParser().encodeResourceToString(result.toOperationOutcome()));
|
||||
throw new UnprocessableEntityException(oo);
|
||||
throw new UnprocessableEntityException(getContext(), oo);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import java.util.Collection;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import ca.uhn.fhir.model.api.IBoundCodeableConcept;
|
||||
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
|
@ -37,10 +39,19 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
|
||||
private IValueSetEnumBinder<T> myBinder;
|
||||
|
||||
/**
|
||||
* @deprecated This constructor is provided only for serialization support. Do not call it directly!
|
||||
*/
|
||||
@Deprecated
|
||||
public BoundCodeableConceptDt() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder) {
|
||||
Validate.notNull(theBinder, "theBinder must not be null");
|
||||
myBinder = theBinder;
|
||||
}
|
||||
|
||||
|
@ -48,6 +59,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
* Constructor
|
||||
*/
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, T theValue) {
|
||||
Validate.notNull(theBinder, "theBinder must not be null");
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValue);
|
||||
}
|
||||
|
@ -56,6 +68,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
* Constructor
|
||||
*/
|
||||
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, Collection<T> theValues) {
|
||||
Validate.notNull(theBinder, "theBinder must not be null");
|
||||
myBinder = theBinder;
|
||||
setValueAsEnum(theValues);
|
||||
}
|
||||
|
@ -70,6 +83,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
* The value to add, or <code>null</code>
|
||||
*/
|
||||
public void setValueAsEnum(Collection<T> theValues) {
|
||||
Validate.notNull(myBinder, "This object does not have a binder. Constructor BoundCodeableConceptDt() should not be called!");
|
||||
getCoding().clear();
|
||||
if (theValues != null) {
|
||||
for (T next : theValues) {
|
||||
|
@ -88,6 +102,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
* The value to add, or <code>null</code>
|
||||
*/
|
||||
public void setValueAsEnum(T theValue) {
|
||||
Validate.notNull(myBinder, "This object does not have a binder. Constructor BoundCodeableConceptDt() should not be called!");
|
||||
getCoding().clear();
|
||||
if (theValue == null) {
|
||||
return;
|
||||
|
@ -107,6 +122,7 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
|
|||
* no codes that match the enum.
|
||||
*/
|
||||
public Set<T> getValueAsEnum() {
|
||||
Validate.notNull(myBinder, "This object does not have a binder. Constructor BoundCodeableConceptDt() should not be called!");
|
||||
Set<T> retVal = new HashSet<T>();
|
||||
for (CodingDt next : getCoding()) {
|
||||
if (next == null) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package ca.uhn.fhir.model.dstu2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
|
||||
public class ModelSerializationTest {
|
||||
|
||||
private static final FhirContext ourCtx = FhirContext.forDstu2();
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws Exception {
|
||||
String input = IOUtils.toString(ModelSerializationTest.class.getResourceAsStream("/diagnosticreport-examples-lab-text(72ac8493-52ac-41bd-8d5d-7258c289b5ea).xml"));
|
||||
|
||||
Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input);
|
||||
Bundle roundTripped = SerializationUtils.roundtrip(parsed);
|
||||
|
||||
IParser p = ourCtx.newXmlParser().setPrettyPrint(true);
|
||||
assertEquals(p.encodeResourceToString(parsed), p.encodeResourceToString(roundTripped));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
|
|||
import org.hl7.fhir.instance.model.Questionnaire.QuestionComponent;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionAnswerComponent;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionnaireAnswersStatus;
|
||||
import org.hl7.fhir.instance.model.Reference;
|
||||
import org.hl7.fhir.instance.model.Resource;
|
||||
import org.hl7.fhir.instance.model.StringType;
|
||||
|
@ -44,11 +45,11 @@ import org.hl7.fhir.instance.utils.WorkerContext;
|
|||
*/
|
||||
public class QuestionnaireAnswersValidator extends BaseValidator {
|
||||
|
||||
/*
|
||||
/* *****************************************************************
|
||||
* Note to anyone working on this class -
|
||||
*
|
||||
* This class has unit tests which run within the HAPI project build. Please sync any changes here to HAPI and ensure that unit tests are run.
|
||||
*/
|
||||
* *****************************************************************/
|
||||
|
||||
private WorkerContext myWorkerCtx;
|
||||
|
||||
|
@ -102,9 +103,17 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
return;
|
||||
}
|
||||
|
||||
QuestionnaireAnswersStatus status = theAnswers.getStatus();
|
||||
boolean validateRequired = false;
|
||||
if (status == QuestionnaireAnswersStatus.COMPLETED || status == QuestionnaireAnswersStatus.AMENDED) {
|
||||
validateRequired = true;
|
||||
} else {
|
||||
hint(theErrors, null, null, false, "Questionnaire has status {0} so ");
|
||||
}
|
||||
|
||||
pathStack.removeLast();
|
||||
pathStack.add("group(0)");
|
||||
validateGroup(theErrors, questionnaire.getGroup(), theAnswers.getGroup(), pathStack, theAnswers);
|
||||
validateGroup(theErrors, questionnaire.getGroup(), theAnswers.getGroup(), pathStack, theAnswers, validateRequired);
|
||||
}
|
||||
|
||||
private Questionnaire getQuestionnaire(QuestionnaireAnswers theAnswers, Reference theQuestionnaireRef) {
|
||||
|
@ -142,7 +151,7 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
}
|
||||
|
||||
private void validateGroup(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
|
||||
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent next : theAnsGroup.getQuestion()) {
|
||||
rule(theErrors, IssueType.INVALID, thePathStack, isNotBlank(next.getLinkId()), "Question found with no linkId");
|
||||
|
@ -155,7 +164,7 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
|
||||
for (int i = 0; i < theQuestGroup.getQuestion().size(); i++) {
|
||||
QuestionComponent nextQuestion = theQuestGroup.getQuestion().get(i);
|
||||
validateQuestion(theErrors, nextQuestion, theAnsGroup, thePathStack, theAnswers);
|
||||
validateQuestion(theErrors, nextQuestion, theAnsGroup, thePathStack, theAnswers, theValidateRequired);
|
||||
}
|
||||
|
||||
// Check that there are no extra answers
|
||||
|
@ -167,12 +176,12 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
thePathStack.remove();
|
||||
}
|
||||
|
||||
validateGroupGroups(theErrors, theQuestGroup, theAnsGroup, thePathStack, theAnswers);
|
||||
validateGroupGroups(theErrors, theQuestGroup, theAnsGroup, thePathStack, theAnswers, theValidateRequired);
|
||||
|
||||
}
|
||||
|
||||
private void validateQuestion(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
String linkId = theQuestion.getLinkId();
|
||||
if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), "Questionnaire is invalid, question found with no link ID")) {
|
||||
return;
|
||||
|
@ -192,32 +201,32 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Multiple answers repetitions found with linkId[{0}]", linkId);
|
||||
}
|
||||
if (answers.size() == 0) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && theValidateRequired), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
return;
|
||||
}
|
||||
|
||||
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion = answers.get(0);
|
||||
try {
|
||||
thePathStack.add("question(" + answers.indexOf(answerQuestion) + ")");
|
||||
validateQuestionAnswers(theErrors, theQuestion, thePathStack, type, answerQuestion, theAnswers);
|
||||
validateQuestionGroups(theErrors, theQuestion, answerQuestion, thePathStack, theAnswers);
|
||||
validateQuestionAnswers(theErrors, theQuestion, thePathStack, type, answerQuestion, theAnswers, theValidateRequired);
|
||||
validateQuestionGroups(theErrors, theQuestion, answerQuestion, thePathStack, theAnswers, theValidateRequired);
|
||||
} finally {
|
||||
thePathStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuestionGroups(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent theAnswerQuestion,
|
||||
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers) {
|
||||
validateGroups(theErrors, theQuestion.getGroup(), theAnswerQuestion.getGroup(), thePathSpec, theAnswers);
|
||||
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
validateGroups(theErrors, theQuestion.getGroup(), theAnswerQuestion.getGroup(), thePathSpec, theAnswers, theValidateRequired);
|
||||
}
|
||||
|
||||
private void validateGroupGroups(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers) {
|
||||
validateGroups(theErrors, theQuestGroup.getGroup(), theAnsGroup.getGroup(), thePathSpec, theAnswers);
|
||||
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
validateGroups(theErrors, theQuestGroup.getGroup(), theAnsGroup.getGroup(), thePathSpec, theAnswers, theValidateRequired);
|
||||
}
|
||||
|
||||
private void validateGroups(List<ValidationMessage> theErrors, List<GroupComponent> theQuestionGroups, List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> theAnswerGroups,
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
Set<String> allowedGroups = new HashSet<String>();
|
||||
for (GroupComponent nextQuestionGroup : theQuestionGroups) {
|
||||
String linkId = nextQuestionGroup.getLinkId();
|
||||
|
@ -225,7 +234,7 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
|
||||
List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> answerGroups = findGroupByLinkId(theAnswerGroups, linkId);
|
||||
if (answerGroups.isEmpty()) {
|
||||
if (nextQuestionGroup.getRequired()) {
|
||||
if (nextQuestionGroup.getRequired() && theValidateRequired) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", linkId);
|
||||
}
|
||||
continue;
|
||||
|
@ -241,7 +250,7 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent nextAnswerGroup : answerGroups) {
|
||||
int index = theAnswerGroups.indexOf(answerGroups.get(1));
|
||||
thePathStack.add("group(" + index + ")");
|
||||
validateGroup(theErrors, nextQuestionGroup, nextAnswerGroup, thePathStack, theAnswers);
|
||||
validateGroup(theErrors, nextQuestionGroup, nextAnswerGroup, thePathStack, theAnswers, theValidateRequired);
|
||||
thePathStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
@ -259,13 +268,13 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
}
|
||||
|
||||
private void validateQuestionAnswers(List<ValidationMessage> theErrors, QuestionComponent theQuestion, LinkedList<String> thePathStack, AnswerFormat type,
|
||||
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion, QuestionnaireAnswers theAnswers) {
|
||||
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
|
||||
String linkId = theQuestion.getLinkId();
|
||||
Set<Class<? extends Type>> allowedAnswerTypes = determineAllowedAnswerTypes(type);
|
||||
if (allowedAnswerTypes.isEmpty()) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, answerQuestion.isEmpty(), "Question with linkId[{0}] has no answer type but an answer was provided", linkId);
|
||||
} else {
|
||||
} else if (theValidateRequired) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(answerQuestion.getAnswer().size() > 1 && !theQuestion.getRepeats()), "Multiple answers to non repeating question with linkId[{0}]",
|
||||
linkId);
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.validation;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
@ -12,12 +13,15 @@ import java.util.List;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.instance.model.Coding;
|
||||
import org.hl7.fhir.instance.model.IdType;
|
||||
import org.hl7.fhir.instance.model.IntegerType;
|
||||
import org.hl7.fhir.instance.model.Questionnaire;
|
||||
import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers;
|
||||
import org.hl7.fhir.instance.model.Reference;
|
||||
import org.hl7.fhir.instance.model.StringType;
|
||||
import org.hl7.fhir.instance.model.ValueSet;
|
||||
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionnaireAnswersStatus;
|
||||
import org.hl7.fhir.instance.utils.WorkerContext;
|
||||
import org.hl7.fhir.instance.validation.QuestionnaireAnswersValidator;
|
||||
import org.hl7.fhir.instance.validation.ValidationMessage;
|
||||
|
@ -26,37 +30,38 @@ import org.junit.Test;
|
|||
import org.mockito.Mockito;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
|
||||
public class QuestionnaireAnswersValidatorIntegrationTest {
|
||||
private static final FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
|
||||
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireAnswersValidatorIntegrationTest.class);
|
||||
|
||||
private IResourceLoader myResourceLoaderMock;
|
||||
|
||||
private FhirValidator myVal;
|
||||
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myResourceLoaderMock = mock(IResourceLoader.class);
|
||||
|
||||
|
||||
FhirQuestionnaireAnswersValidator qaVal = new FhirQuestionnaireAnswersValidator();
|
||||
qaVal.setResourceLoader(myResourceLoaderMock);
|
||||
|
||||
|
||||
myVal = ourCtx.newValidator();
|
||||
myVal.setValidateAgainstStandardSchema(false);
|
||||
myVal.setValidateAgainstStandardSchematron(false);
|
||||
myVal.registerValidatorModule(qaVal);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAnswerWithWrongType() {
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
|
||||
|
||||
|
||||
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
|
||||
|
||||
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
|
||||
|
@ -66,10 +71,63 @@ public class QuestionnaireAnswersValidatorIntegrationTest {
|
|||
assertThat(result.getMessages().toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiredOnlyTestedForFinishedQuestionnaires() {
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
|
||||
GroupComponent qg = q.getGroup().addGroup().setLinkId("link1").setRequired(true);
|
||||
qg.addQuestion().setLinkId("link2").setRequired(false).setType(AnswerFormat.BOOLEAN);
|
||||
|
||||
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
|
||||
|
||||
// Wrong type
|
||||
{
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new IntegerType(123));
|
||||
|
||||
ValidationResult result = myVal.validateWithResult(qa);
|
||||
ourLog.info(result.getMessages().toString());
|
||||
assertThat(result.getMessages().toString(), containsString("Answer to question with linkId[link0] found of type [IntegerType] but this is invalid for question of type [boolean]"));
|
||||
}
|
||||
|
||||
// Not populated, no status
|
||||
{
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
|
||||
ValidationResult result = myVal.validateWithResult(qa);
|
||||
ourLog.info(result.getMessages().toString());
|
||||
assertThat(result.getMessages(), empty());
|
||||
}
|
||||
|
||||
// Not populated, partial status
|
||||
{
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
qa.setStatus(QuestionnaireAnswersStatus.INPROGRESS);
|
||||
|
||||
ValidationResult result = myVal.validateWithResult(qa);
|
||||
ourLog.info(result.getMessages().toString());
|
||||
assertThat(result.getMessages(), empty());
|
||||
}
|
||||
|
||||
// Not populated, finished status
|
||||
{
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
qa.setStatus(QuestionnaireAnswersStatus.COMPLETED);
|
||||
|
||||
ValidationResult result = myVal.validateWithResult(qa);
|
||||
ourLog.info(result.getMessages().toString());
|
||||
assertThat(result.getMessages().toString(), containsString("Missing answer to required question with linkId[link0]"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodedAnswer() {
|
||||
String questionnaireRef = "http://example.com/Questionnaire/q1";
|
||||
|
||||
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset/ValueSet/123"));
|
||||
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
|
||||
|
@ -77,45 +135,45 @@ public class QuestionnaireAnswersValidatorIntegrationTest {
|
|||
ValueSet options = new ValueSet();
|
||||
options.getDefine().setSystem("urn:system").addConcept().setCode("code0");
|
||||
when(myResourceLoaderMock.load(Mockito.eq(ValueSet.class), Mockito.eq(new IdType("http://somevalueset/ValueSet/123")))).thenReturn(options);
|
||||
|
||||
|
||||
QuestionnaireAnswers qa;
|
||||
|
||||
|
||||
// Good code
|
||||
|
||||
|
||||
qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference(questionnaireRef);
|
||||
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
|
||||
ValidationResult result = myVal.validateWithResult(qa);
|
||||
assertEquals(result.getMessages().toString(), 0, result.getMessages().size());
|
||||
|
||||
|
||||
// Bad code
|
||||
|
||||
|
||||
qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference(questionnaireRef);
|
||||
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
|
||||
result = myVal.validateWithResult(qa);
|
||||
ourLog.info(result.getMessages().toString());
|
||||
assertThat(result.getMessages().toString(), containsString("myLocationString=QuestionnaireAnswers.group(0).question(0).answer(0)"));
|
||||
assertThat(result.getMessages().toString(), containsString("myMessage=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset/ValueSet/123]"));
|
||||
|
||||
assertThat(result.getMessages().toString(),
|
||||
containsString("myMessage=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset/ValueSet/123]"));
|
||||
|
||||
result.toOperationOutcome();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnknownValueSet() {
|
||||
String questionnaireRef = "http://example.com/Questionnaire/q1";
|
||||
|
||||
|
||||
Questionnaire q = new Questionnaire();
|
||||
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset/ValueSet/123"));
|
||||
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
|
||||
|
||||
|
||||
when(myResourceLoaderMock.load(Mockito.eq(ValueSet.class), Mockito.eq(new IdType("http://somevalueset/ValueSet/123")))).thenThrow(new ResourceNotFoundException("Unknown"));
|
||||
|
||||
|
||||
QuestionnaireAnswers qa;
|
||||
|
||||
|
||||
// Bad code
|
||||
|
||||
|
||||
qa = new QuestionnaireAnswers();
|
||||
qa.getQuestionnaire().setReference(questionnaireRef);
|
||||
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.hl7.fhir.instance.model.Reference;
|
|||
import org.hl7.fhir.instance.model.StringType;
|
||||
import org.hl7.fhir.instance.model.ValueSet;
|
||||
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
|
||||
import org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionnaireAnswersStatus;
|
||||
import org.hl7.fhir.instance.utils.WorkerContext;
|
||||
import org.hl7.fhir.instance.validation.QuestionnaireAnswersValidator;
|
||||
import org.hl7.fhir.instance.validation.ValidationMessage;
|
||||
|
@ -99,6 +100,7 @@ public class QuestionnaireAnswersValidatorTest {
|
|||
q.getGroup().addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.STRING);
|
||||
|
||||
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||
qa.setStatus(QuestionnaireAnswersStatus.COMPLETED);
|
||||
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
|
||||
|
||||
|
|
|
@ -215,11 +215,11 @@ public class TinderStructuresMojo extends AbstractMojo {
|
|||
// ProfileParser pp = new ProfileParser();
|
||||
// pp.parseSingleProfile(new File("../hapi-tinder-test/src/test/resources/profile/patient.xml"), "http://foo");
|
||||
|
||||
ValueSetGenerator vsp = new ValueSetGenerator("dstu2");
|
||||
ValueSetGenerator vsp = new ValueSetGenerator("dstu");
|
||||
// vsp.setResourceValueSetFiles(theResourceValueSetFiles);Directory("src/main/resources/vs/");
|
||||
vsp.parse();
|
||||
|
||||
DatatypeGeneratorUsingSpreadsheet dtp = new DatatypeGeneratorUsingSpreadsheet("dstu2", ".");
|
||||
DatatypeGeneratorUsingSpreadsheet dtp = new DatatypeGeneratorUsingSpreadsheet("dstu", ".");
|
||||
dtp.parse();
|
||||
dtp.markResourcesForImports();
|
||||
dtp.bindValueSets(vsp);
|
||||
|
@ -227,8 +227,8 @@ public class TinderStructuresMojo extends AbstractMojo {
|
|||
|
||||
String dtOutputDir = "target/generated-sources/tinder/ca/uhn/fhir/model/dev/composite";
|
||||
|
||||
ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet("dstu2", ".");
|
||||
rp.setBaseResourceNames(Arrays.asList( "composition", "list"
|
||||
ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet("dstu", ".");
|
||||
rp.setBaseResourceNames(Arrays.asList( "patient", "list"
|
||||
// //, "contract"
|
||||
// "valueset", "organization", "location"
|
||||
// , "observation", "conformance"
|
||||
|
|
|
@ -308,7 +308,7 @@ public class ValueSetGenerator {
|
|||
|
||||
public static void main(String[] args) throws FileNotFoundException, IOException {
|
||||
|
||||
ValueSetGenerator p = new ValueSetGenerator("dev");
|
||||
ValueSetGenerator p = new ValueSetGenerator("dstu1");
|
||||
p.parse();
|
||||
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@
|
|||
Operations in server generated conformance statement should only
|
||||
appear once per name, since the name needs to be unique.
|
||||
</action>
|
||||
<action>
|
||||
Resources and datatypes are now serializable. This is an
|
||||
experimental feature which hasn't yet been extensively tested. Please test and give us your feedback!
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.1" date="2015-07-13">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue