Fix #364 - Allow serializing custom types that contain custom datatypes
This commit is contained in:
parent
021025ffa9
commit
20b6994cc8
|
@ -32,6 +32,7 @@ import org.apache.commons.lang3.Validate;
|
|||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.fhir.model.api.IElement;
|
||||
|
@ -64,7 +65,10 @@ import ca.uhn.fhir.validation.FhirValidator;
|
|||
* Important usage notes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li>
|
||||
* <li>
|
||||
* Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing
|
||||
* threads, except for the {@link #registerCustomType} and {@link #registerCustomTypes} methods.
|
||||
* </li>
|
||||
* <li>
|
||||
* Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode
|
||||
* to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance
|
||||
|
@ -80,6 +84,7 @@ public class FhirContext {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
|
||||
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
|
||||
private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
|
||||
private ArrayList<Class<? extends IBase>> myCustomTypes;
|
||||
private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>();
|
||||
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
|
||||
private HapiLocalizer myLocalizer = new HapiLocalizer();
|
||||
|
@ -92,33 +97,55 @@ public class FhirContext {
|
|||
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
|
||||
private final IFhirVersion myVersion;
|
||||
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor. In most cases this is the right constructor to use.
|
||||
* @deprecated It is recommended that you use one of the static initializer methods instead
|
||||
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
|
||||
*/
|
||||
@Deprecated
|
||||
public FhirContext() {
|
||||
this(EMPTY_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated It is recommended that you use one of the static initializer methods instead
|
||||
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
|
||||
*/
|
||||
@Deprecated
|
||||
public FhirContext(Class<? extends IBaseResource> theResourceType) {
|
||||
this(toCollection(theResourceType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated It is recommended that you use one of the static initializer methods instead
|
||||
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
|
||||
*/
|
||||
@Deprecated
|
||||
public FhirContext(Class<?>... theResourceTypes) {
|
||||
this(toCollection(theResourceTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated It is recommended that you use one of the static initializer methods instead
|
||||
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
|
||||
*/
|
||||
@Deprecated
|
||||
public FhirContext(Collection<Class<? extends IBaseResource>> theResourceTypes) {
|
||||
this(null, theResourceTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* In most cases it is recommended that you use one of the static initializer methods instead
|
||||
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}, but
|
||||
* this method can also be used if you wish to supply the version programmatically.
|
||||
*/
|
||||
public FhirContext(FhirVersionEnum theVersion) {
|
||||
this(theVersion, null);
|
||||
}
|
||||
|
||||
private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
|
||||
VersionUtil.getVersion();
|
||||
|
||||
|
||||
if (theVersion != null) {
|
||||
if (!theVersion.isPresentOnClasspath()) {
|
||||
throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
|
||||
|
@ -147,6 +174,13 @@ public class FhirContext {
|
|||
return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion);
|
||||
}
|
||||
|
||||
private void ensureCustomTypeList() {
|
||||
myClassToElementDefinition.clear();
|
||||
if (myCustomTypes == null) {
|
||||
myCustomTypes = new ArrayList<Class<? extends IBase>>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When encoding resources, this setting configures the parser to include
|
||||
* an entry in the resource's metadata section which indicates which profile(s) the
|
||||
|
@ -190,7 +224,7 @@ public class FhirContext {
|
|||
public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) {
|
||||
return myNameToElementDefinition.get(theElementName.toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
/** For unit tests only */
|
||||
int getElementDefinitionCount() {
|
||||
return myClassToElementDefinition.size();
|
||||
|
@ -283,7 +317,7 @@ public class FhirContext {
|
|||
@SuppressWarnings("unchecked")
|
||||
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
||||
Validate.notBlank(theResourceName, "theResourceName must not be blank");
|
||||
|
||||
|
||||
String resourceName = theResourceName.toLowerCase();
|
||||
RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName);
|
||||
|
||||
|
@ -317,8 +351,9 @@ public class FhirContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the restful client factory. If no factory has been set, this will be initialized with
|
||||
* Get the restful client factory. If no factory has been set, this will be initialized with
|
||||
* a new ApacheRestfulClientFactory.
|
||||
*
|
||||
* @return the factory used to create the restful clients
|
||||
*/
|
||||
public IRestfulClientFactory getRestfulClientFactory() {
|
||||
|
@ -385,12 +420,12 @@ public class FhirContext {
|
|||
* </p>
|
||||
*
|
||||
* @param theClientType
|
||||
* The client type, which is an interface type to be instantiated
|
||||
* The client type, which is an interface type to be instantiated
|
||||
* @param theServerBase
|
||||
* The URL of the base for the restful FHIR server to connect to
|
||||
* The URL of the base for the restful FHIR server to connect to
|
||||
* @return A newly created client
|
||||
* @throws ConfigurationException
|
||||
* If the interface type is not an interface
|
||||
* If the interface type is not an interface
|
||||
*/
|
||||
public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
|
||||
return getRestfulClientFactory().newClient(theClientType, theServerBase);
|
||||
|
@ -407,7 +442,7 @@ public class FhirContext {
|
|||
* </p>
|
||||
*
|
||||
* @param theServerBase
|
||||
* The URL of the base for the restful FHIR server to connect to
|
||||
* The URL of the base for the restful FHIR server to connect to
|
||||
*/
|
||||
public IGenericClient newRestfulGenericClient(String theServerBase) {
|
||||
return getRestfulClientFactory().newGenericClient(theServerBase);
|
||||
|
@ -420,7 +455,7 @@ public class FhirContext {
|
|||
/**
|
||||
* Create a new validator instance.
|
||||
* <p>
|
||||
* Note on thread safety: Validators are thread safe, you may use a single validator
|
||||
* Note on thread safety: Validators are thread safe, you may use a single validator
|
||||
* in multiple threads. (This is in contrast to parsers)
|
||||
* </p>
|
||||
*/
|
||||
|
@ -448,6 +483,48 @@ public class FhirContext {
|
|||
return new XmlParser(this, myParserErrorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method may be used to register a custom resource or datatype. Note that by using
|
||||
* custom types, you are creating a system that will not interoperate with other systems that
|
||||
* do not know about your custom type. There are valid reasons however for wanting to create
|
||||
* custom types and this method can be used to enable them.
|
||||
* <p>
|
||||
* <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
|
||||
* threads are able to call any methods on this context.
|
||||
* </p>
|
||||
*
|
||||
* @param theType
|
||||
* The custom type to add (must not be <code>null</code>)
|
||||
*/
|
||||
public void registerCustomType(Class<? extends IBase> theType) {
|
||||
Assert.notNull(theType, "theType must not be null");
|
||||
|
||||
ensureCustomTypeList();
|
||||
myCustomTypes.add(theType);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method may be used to register a custom resource or datatype. Note that by using
|
||||
* custom types, you are creating a system that will not interoperate with other systems that
|
||||
* do not know about your custom type. There are valid reasons however for wanting to create
|
||||
* custom types and this method can be used to enable them.
|
||||
* <p>
|
||||
* <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
|
||||
* threads are able to call any methods on this context.
|
||||
* </p>
|
||||
*
|
||||
* @param theTypes
|
||||
* The custom types to add (must not be <code>null</code> or contain null elements in the collection)
|
||||
*/
|
||||
public void registerCustomTypes(Collection<Class<? extends IBase>> theTypes) {
|
||||
Assert.notNull(theTypes, "theTypes must not be null");
|
||||
Assert.noNullElements(theTypes.toArray(), "theTypes must not contain any null elements");
|
||||
|
||||
ensureCustomTypeList();
|
||||
|
||||
myCustomTypes.addAll(theTypes);
|
||||
}
|
||||
|
||||
private BaseRuntimeElementDefinition<?> scanDatatype(Class<? extends IElement> theResourceType) {
|
||||
ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
|
||||
resourceTypes.add(theResourceType);
|
||||
|
@ -463,7 +540,17 @@ public class FhirContext {
|
|||
}
|
||||
|
||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
|
||||
ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, theResourceTypes);
|
||||
|
||||
List<Class<? extends IBase>> typesToScan = new ArrayList<Class<? extends IBase>>();
|
||||
if (theResourceTypes != null) {
|
||||
typesToScan.addAll(theResourceTypes);
|
||||
}
|
||||
if (myCustomTypes != null) {
|
||||
typesToScan.addAll(myCustomTypes);
|
||||
myCustomTypes = null;
|
||||
}
|
||||
|
||||
ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, typesToScan);
|
||||
if (myRuntimeChildUndeclaredExtensionDefinition == null) {
|
||||
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
|
||||
}
|
||||
|
@ -475,7 +562,7 @@ public class FhirContext {
|
|||
Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
|
||||
nameToResourceDefinition.putAll(myNameToResourceDefinition);
|
||||
nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition());
|
||||
|
||||
|
||||
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
|
||||
classToElementDefinition.putAll(myClassToElementDefinition);
|
||||
classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
|
||||
|
@ -502,7 +589,7 @@ public class FhirContext {
|
|||
|
||||
return classToElementDefinition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When encoding resources, this setting configures the parser to include
|
||||
* an entry in the resource's metadata section which indicates which profile(s) the
|
||||
|
@ -510,7 +597,7 @@ public class FhirContext {
|
|||
* <p>
|
||||
* This feature is intended for situations where custom resource types are being used,
|
||||
* avoiding the need to manually add profile declarations for these custom types.
|
||||
* </p>
|
||||
* </p>
|
||||
* <p>
|
||||
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
|
||||
* for more information on using custom types.
|
||||
|
@ -530,7 +617,7 @@ public class FhirContext {
|
|||
|
||||
/**
|
||||
* Sets the default type which will be used when parsing a resource that is found to be
|
||||
* of the given profile.
|
||||
* of the given profile.
|
||||
* <p>
|
||||
* For example, this method is invoked with the profile string of
|
||||
* <code>"http://example.com/some_patient_profile"</code> and the type of <code>MyPatient.class</code>,
|
||||
|
@ -538,8 +625,11 @@ public class FhirContext {
|
|||
* the <code>MyPatient</code> type will be used unless otherwise specified.
|
||||
* </p>
|
||||
*
|
||||
* @param theProfile The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be <code>null</code> or empty.
|
||||
* @param theClass The resource type. Must not be <code>null</code> or empty.
|
||||
* @param theProfile
|
||||
* The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be
|
||||
* <code>null</code> or empty.
|
||||
* @param theClass
|
||||
* The resource type. Must not be <code>null</code> or empty.
|
||||
*/
|
||||
public void setDefaultTypeForProfile(String theProfile, Class<? extends IBaseResource> theClass) {
|
||||
Validate.notBlank(theProfile, "theProfile must not be null or empty");
|
||||
|
@ -562,7 +652,8 @@ public class FhirContext {
|
|||
/**
|
||||
* Sets a parser error handler to use by default on all parsers
|
||||
*
|
||||
* @param theParserErrorHandler The error handler
|
||||
* @param theParserErrorHandler
|
||||
* The error handler
|
||||
*/
|
||||
public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
|
||||
Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
|
||||
|
@ -571,6 +662,7 @@ public class FhirContext {
|
|||
|
||||
/**
|
||||
* Set the restful client factory
|
||||
*
|
||||
* @param theRestfulClientFactory
|
||||
*/
|
||||
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
|
||||
|
@ -605,7 +697,8 @@ public class FhirContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference Implementation Structures)
|
||||
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference
|
||||
* Implementation Structures)
|
||||
*/
|
||||
public static FhirContext forDstu2Hl7Org() {
|
||||
return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
|
||||
|
|
|
@ -102,7 +102,7 @@ class ModelScanner {
|
|||
private Set<Class<? extends IBase>> myVersionTypes;
|
||||
|
||||
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
|
||||
Collection<Class<? extends IElement>> theResourceTypes) throws ConfigurationException {
|
||||
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
|
||||
myContext = theContext;
|
||||
myVersion = theVersion;
|
||||
Set<Class<? extends IBase>> toScan;
|
||||
|
@ -467,7 +467,7 @@ class ModelScanner {
|
|||
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
|
||||
*/
|
||||
if (order == Child.ORDER_UNKNOWN) {
|
||||
order = Integer.MIN_VALUE;
|
||||
order = Integer.valueOf(0);
|
||||
while (orderMap.containsKey(order)) {
|
||||
order++;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package ca.uhn.fhir.parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
|
||||
import ca.uhn.fhir.model.api.ICompositeDatatype;
|
||||
import ca.uhn.fhir.model.api.IElement;
|
||||
import ca.uhn.fhir.model.api.IResourceBlock;
|
||||
import ca.uhn.fhir.model.api.annotation.Block;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||
import ca.uhn.fhir.model.dstu2.resource.BaseResource;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
|
||||
/**
|
||||
* See #364
|
||||
*/
|
||||
@ResourceDef(name = "CustomResource", profile = "http://hl7.org/fhir/profiles/custom-resource", id = "custom-resource")
|
||||
public class CustomResource364Dstu2 extends BaseResource implements IBaseOperationOutcome {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Child(name = "baseValue", min = 1, max = Child.MAX_UNLIMITED, type= {})
|
||||
private IElement baseValues;
|
||||
|
||||
@Override
|
||||
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
|
||||
return ElementUtil.allPopulatedChildElements(theType, baseValues);
|
||||
}
|
||||
|
||||
public IElement getBaseValues() {
|
||||
return baseValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return "CustomResource";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirVersionEnum getStructureFhirVersionEnum() {
|
||||
return FhirVersionEnum.DSTU2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ElementUtil.isEmpty(baseValues);
|
||||
}
|
||||
|
||||
public void setBaseValues(IElement theValue) {
|
||||
this.baseValues = theValue;
|
||||
}
|
||||
|
||||
@DatatypeDef(name="CustomDate")
|
||||
public static class CustomResource364CustomDate extends BaseIdentifiableElement implements ICompositeDatatype {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Child(name = "date", order = 0, min = 1, max = 1, type = { DateTimeDt.class })
|
||||
private DateTimeDt date;
|
||||
|
||||
@Override
|
||||
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
|
||||
return ElementUtil.allPopulatedChildElements(theType, date);
|
||||
}
|
||||
|
||||
public DateTimeDt getDate() {
|
||||
if (date == null)
|
||||
date = new DateTimeDt();
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ElementUtil.isEmpty(date);
|
||||
}
|
||||
|
||||
public CustomResource364CustomDate setDate(DateTimeDt theValue) {
|
||||
date = theValue;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
|
|||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.parser.CustomResource364Dstu2.CustomResource364CustomDate;
|
||||
import ca.uhn.fhir.rest.server.AddProfileTagEnum;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -42,6 +43,60 @@ public class CustomTypeDstu2Test {
|
|||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* See #364
|
||||
*/
|
||||
@Test
|
||||
public void testCustomTypeWithCustomDatatype() {
|
||||
FhirContext context = FhirContext.forDstu2();
|
||||
context.registerCustomType(CustomResource364Dstu2.class);
|
||||
context.registerCustomType(CustomResource364CustomDate.class);
|
||||
IParser parser = context.newXmlParser();
|
||||
|
||||
CustomResource364Dstu2 resource = new CustomResource364Dstu2();
|
||||
resource.setBaseValues(new CustomResource364CustomDate().setDate(new DateTimeDt("2016-05-13")));
|
||||
|
||||
String xml = parser.encodeResourceToString(resource);
|
||||
ourLog.info(xml);
|
||||
|
||||
//@formatter:on
|
||||
assertThat(xml, stringContainsInOrder(
|
||||
"<CustomResource xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta><profile value=\"http://hl7.org/fhir/profiles/custom-resource\"/></meta>",
|
||||
"<baseValueCustomDate><date value=\"2016-05-13\"/></baseValueCustomDate>",
|
||||
"</CustomResource>"
|
||||
));
|
||||
//@formatter:on
|
||||
|
||||
CustomResource364Dstu2 parsedResource = parser.parseResource(CustomResource364Dstu2.class, xml);
|
||||
assertEquals("2016-05-13", ((CustomResource364CustomDate)parsedResource.getBaseValues()).getDate().getValueAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #364
|
||||
*/
|
||||
@Test
|
||||
public void testCustomTypeWithPrimitiveType() {
|
||||
FhirContext context = FhirContext.forDstu2();
|
||||
IParser parser = context.newXmlParser();
|
||||
|
||||
CustomResource364Dstu2 resource = new CustomResource364Dstu2();
|
||||
resource.setBaseValues(new StringDt("2016-05-13"));
|
||||
|
||||
String xml = parser.encodeResourceToString(resource);
|
||||
|
||||
//@formatter:on
|
||||
assertThat(xml, stringContainsInOrder(
|
||||
"<CustomResource xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta><profile value=\"http://hl7.org/fhir/profiles/custom-resource\"/></meta>",
|
||||
"<baseValueString value=\"2016-05-13\"/>",
|
||||
"</CustomResource>"
|
||||
));
|
||||
//@formatter:on
|
||||
|
||||
CustomResource364Dstu2 parsedResource = parser.parseResource(CustomResource364Dstu2.class, xml);
|
||||
assertEquals("2016-05-13", ((StringDt)parsedResource.getBaseValues()).getValueAsString());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package ca.uhn.fhir.parser;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.hl7.fhir.dstu3.model.DomainResource;
|
||||
import org.hl7.fhir.dstu3.model.ResourceType;
|
||||
import org.hl7.fhir.dstu3.model.Type;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.IElement;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
|
||||
/**
|
||||
* This is an example of a custom resource that also uses a custom
|
||||
* datatype.
|
||||
*
|
||||
* See #364
|
||||
*/
|
||||
@ResourceDef(name = "CustomResource", profile = "http://hl7.org/fhir/profiles/custom-resource", id = "custom-resource")
|
||||
public class CustomResource364Dstu3 extends DomainResource {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Child(name = "baseValue", min = 1, max = Child.MAX_UNLIMITED, type= {})
|
||||
private Type baseValues;
|
||||
|
||||
public Type getBaseValues() {
|
||||
return baseValues;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FhirVersionEnum getStructureFhirVersionEnum() {
|
||||
return FhirVersionEnum.DSTU3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ElementUtil.isEmpty(baseValues);
|
||||
}
|
||||
|
||||
public void setBaseValues(Type theValue) {
|
||||
this.baseValues = theValue;
|
||||
}
|
||||
|
||||
@DatatypeDef(name="CustomDate")
|
||||
public static class CustomResource364CustomDate extends Type implements ICompositeType {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Child(name = "date", order = 0, min = 1, max = 1, type = { DateTimeDt.class })
|
||||
private DateTimeType date;
|
||||
|
||||
|
||||
public DateTimeType getDate() {
|
||||
if (date == null)
|
||||
date = new DateTimeType();
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ElementUtil.isEmpty(date);
|
||||
}
|
||||
|
||||
public CustomResource364CustomDate setDate(DateTimeType theValue) {
|
||||
date = theValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CustomResource364CustomDate typedCopy() {
|
||||
CustomResource364CustomDate retVal = new CustomResource364CustomDate();
|
||||
super.copyValues(retVal);
|
||||
retVal.date = date;
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomResource364Dstu3 copy() {
|
||||
CustomResource364Dstu3 retVal = new CustomResource364Dstu3();
|
||||
super.copyValues(retVal);
|
||||
retVal.baseValues = baseValues;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResourceType getResourceType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,11 +11,13 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.hl7.fhir.dstu3.model.Medication;
|
||||
import org.hl7.fhir.dstu3.model.MedicationOrder;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Quantity;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -27,6 +29,7 @@ import ca.uhn.fhir.model.api.annotation.Extension;
|
|||
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.parser.CustomResource364Dstu3.CustomResource364CustomDate;
|
||||
import ca.uhn.fhir.rest.server.AddProfileTagEnum;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -47,6 +50,63 @@ public class CustomTypeDstu3Test {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* See #364
|
||||
*/
|
||||
@Test
|
||||
public void testCustomTypeWithCustomDatatype() {
|
||||
FhirContext context = FhirContext.forDstu3();
|
||||
context.registerCustomType(CustomResource364Dstu3.class);
|
||||
context.registerCustomType(CustomResource364CustomDate.class);
|
||||
IParser parser = context.newXmlParser();
|
||||
|
||||
CustomResource364Dstu3 resource = new CustomResource364Dstu3();
|
||||
resource.setBaseValues(new CustomResource364CustomDate().setDate(new DateTimeType("2016-05-13")));
|
||||
|
||||
String xml = parser.encodeResourceToString(resource);
|
||||
ourLog.info(xml);
|
||||
|
||||
//@formatter:on
|
||||
assertThat(xml, stringContainsInOrder(
|
||||
"<CustomResource xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta><profile value=\"http://hl7.org/fhir/profiles/custom-resource\"/></meta>",
|
||||
"<baseValueCustomDate><date value=\"2016-05-13\"/></baseValueCustomDate>",
|
||||
"</CustomResource>"
|
||||
));
|
||||
//@formatter:on
|
||||
|
||||
CustomResource364Dstu3 parsedResource = parser.parseResource(CustomResource364Dstu3.class, xml);
|
||||
assertEquals("2016-05-13", ((CustomResource364CustomDate)parsedResource.getBaseValues()).getDate().getValueAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #364
|
||||
*/
|
||||
@Test
|
||||
public void testCustomTypeWithPrimitiveType() {
|
||||
FhirContext context = FhirContext.forDstu3();
|
||||
context.registerCustomTypes(new ArrayList<Class<? extends IBase>>());
|
||||
|
||||
IParser parser = context.newXmlParser();
|
||||
|
||||
CustomResource364Dstu3 resource = new CustomResource364Dstu3();
|
||||
resource.setBaseValues(new StringType("2016-05-13"));
|
||||
|
||||
String xml = parser.encodeResourceToString(resource);
|
||||
|
||||
//@formatter:on
|
||||
assertThat(xml, stringContainsInOrder(
|
||||
"<CustomResource xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta><profile value=\"http://hl7.org/fhir/profiles/custom-resource\"/></meta>",
|
||||
"<baseValueString value=\"2016-05-13\"/>",
|
||||
"</CustomResource>"
|
||||
));
|
||||
//@formatter:on
|
||||
|
||||
CustomResource364Dstu3 parsedResource = parser.parseResource(CustomResource364Dstu3.class, xml);
|
||||
assertEquals("2016-05-13", ((StringType)parsedResource.getBaseValues()).getValueAsString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void parseBundleWithResourceDirective() {
|
||||
|
|
|
@ -208,6 +208,9 @@
|
|||
(e.g. Patient with an active value of "1") the server should return an HTTP 400, not
|
||||
an HTTP 500. Thanks to Jim Steel for reporting!
|
||||
</action>
|
||||
<action type="fix" issue="364">
|
||||
Enable parsers to parse and serialize custom resources that contain custom datatypes
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.5" date="2016-04-20">
|
||||
<action type="fix" issue="339">
|
||||
|
|
Loading…
Reference in New Issue