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.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||||
import ca.uhn.fhir.model.api.IElement;
|
import ca.uhn.fhir.model.api.IElement;
|
||||||
|
@ -64,7 +65,10 @@ import ca.uhn.fhir.validation.FhirValidator;
|
||||||
* Important usage notes:
|
* Important usage notes:
|
||||||
* </p>
|
* </p>
|
||||||
* <ul>
|
* <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>
|
* <li>
|
||||||
* Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode
|
* 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
|
* 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 static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
|
||||||
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
|
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
|
||||||
private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
|
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 Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>();
|
||||||
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
|
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
|
||||||
private HapiLocalizer myLocalizer = new HapiLocalizer();
|
private HapiLocalizer myLocalizer = new HapiLocalizer();
|
||||||
|
@ -92,33 +97,55 @@ public class FhirContext {
|
||||||
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
|
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
|
||||||
private final IFhirVersion myVersion;
|
private final IFhirVersion myVersion;
|
||||||
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
|
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() {
|
public FhirContext() {
|
||||||
this(EMPTY_LIST);
|
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) {
|
public FhirContext(Class<? extends IBaseResource> theResourceType) {
|
||||||
this(toCollection(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) {
|
public FhirContext(Class<?>... theResourceTypes) {
|
||||||
this(toCollection(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) {
|
public FhirContext(Collection<Class<? extends IBaseResource>> theResourceTypes) {
|
||||||
this(null, 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) {
|
public FhirContext(FhirVersionEnum theVersion) {
|
||||||
this(theVersion, null);
|
this(theVersion, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
|
private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
|
||||||
VersionUtil.getVersion();
|
VersionUtil.getVersion();
|
||||||
|
|
||||||
if (theVersion != null) {
|
if (theVersion != null) {
|
||||||
if (!theVersion.isPresentOnClasspath()) {
|
if (!theVersion.isPresentOnClasspath()) {
|
||||||
throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
|
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);
|
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
|
* When encoding resources, this setting configures the parser to include
|
||||||
* an entry in the resource's metadata section which indicates which profile(s) the
|
* 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) {
|
public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) {
|
||||||
return myNameToElementDefinition.get(theElementName.toLowerCase());
|
return myNameToElementDefinition.get(theElementName.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** For unit tests only */
|
/** For unit tests only */
|
||||||
int getElementDefinitionCount() {
|
int getElementDefinitionCount() {
|
||||||
return myClassToElementDefinition.size();
|
return myClassToElementDefinition.size();
|
||||||
|
@ -283,7 +317,7 @@ public class FhirContext {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
||||||
Validate.notBlank(theResourceName, "theResourceName must not be blank");
|
Validate.notBlank(theResourceName, "theResourceName must not be blank");
|
||||||
|
|
||||||
String resourceName = theResourceName.toLowerCase();
|
String resourceName = theResourceName.toLowerCase();
|
||||||
RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName);
|
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.
|
* a new ApacheRestfulClientFactory.
|
||||||
|
*
|
||||||
* @return the factory used to create the restful clients
|
* @return the factory used to create the restful clients
|
||||||
*/
|
*/
|
||||||
public IRestfulClientFactory getRestfulClientFactory() {
|
public IRestfulClientFactory getRestfulClientFactory() {
|
||||||
|
@ -385,12 +420,12 @@ public class FhirContext {
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theClientType
|
* @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
|
* @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
|
* @return A newly created client
|
||||||
* @throws ConfigurationException
|
* @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) {
|
public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
|
||||||
return getRestfulClientFactory().newClient(theClientType, theServerBase);
|
return getRestfulClientFactory().newClient(theClientType, theServerBase);
|
||||||
|
@ -407,7 +442,7 @@ public class FhirContext {
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theServerBase
|
* @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) {
|
public IGenericClient newRestfulGenericClient(String theServerBase) {
|
||||||
return getRestfulClientFactory().newGenericClient(theServerBase);
|
return getRestfulClientFactory().newGenericClient(theServerBase);
|
||||||
|
@ -420,7 +455,7 @@ public class FhirContext {
|
||||||
/**
|
/**
|
||||||
* Create a new validator instance.
|
* Create a new validator instance.
|
||||||
* <p>
|
* <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)
|
* in multiple threads. (This is in contrast to parsers)
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
@ -448,6 +483,48 @@ public class FhirContext {
|
||||||
return new XmlParser(this, myParserErrorHandler);
|
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) {
|
private BaseRuntimeElementDefinition<?> scanDatatype(Class<? extends IElement> theResourceType) {
|
||||||
ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
|
ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
|
||||||
resourceTypes.add(theResourceType);
|
resourceTypes.add(theResourceType);
|
||||||
|
@ -463,7 +540,17 @@ public class FhirContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
|
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) {
|
if (myRuntimeChildUndeclaredExtensionDefinition == null) {
|
||||||
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
|
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
|
||||||
}
|
}
|
||||||
|
@ -475,7 +562,7 @@ public class FhirContext {
|
||||||
Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
|
Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
|
||||||
nameToResourceDefinition.putAll(myNameToResourceDefinition);
|
nameToResourceDefinition.putAll(myNameToResourceDefinition);
|
||||||
nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition());
|
nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition());
|
||||||
|
|
||||||
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
|
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
|
||||||
classToElementDefinition.putAll(myClassToElementDefinition);
|
classToElementDefinition.putAll(myClassToElementDefinition);
|
||||||
classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
|
classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
|
||||||
|
@ -502,7 +589,7 @@ public class FhirContext {
|
||||||
|
|
||||||
return classToElementDefinition;
|
return classToElementDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When encoding resources, this setting configures the parser to include
|
* When encoding resources, this setting configures the parser to include
|
||||||
* an entry in the resource's metadata section which indicates which profile(s) the
|
* an entry in the resource's metadata section which indicates which profile(s) the
|
||||||
|
@ -510,7 +597,7 @@ public class FhirContext {
|
||||||
* <p>
|
* <p>
|
||||||
* This feature is intended for situations where custom resource types are being used,
|
* 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.
|
* avoiding the need to manually add profile declarations for these custom types.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
|
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
|
||||||
* for more information on using custom types.
|
* 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
|
* 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>
|
* <p>
|
||||||
* For example, this method is invoked with the profile string of
|
* 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>,
|
* <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.
|
* the <code>MyPatient</code> type will be used unless otherwise specified.
|
||||||
* </p>
|
* </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 theProfile
|
||||||
* @param theClass The resource type. Must not be <code>null</code> or empty.
|
* 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) {
|
public void setDefaultTypeForProfile(String theProfile, Class<? extends IBaseResource> theClass) {
|
||||||
Validate.notBlank(theProfile, "theProfile must not be null or empty");
|
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
|
* 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) {
|
public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
|
||||||
Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
|
Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
|
||||||
|
@ -571,6 +662,7 @@ public class FhirContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the restful client factory
|
* Set the restful client factory
|
||||||
|
*
|
||||||
* @param theRestfulClientFactory
|
* @param theRestfulClientFactory
|
||||||
*/
|
*/
|
||||||
public void setRestfulClientFactory(IRestfulClientFactory 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() {
|
public static FhirContext forDstu2Hl7Org() {
|
||||||
return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
|
return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
|
||||||
|
|
|
@ -102,7 +102,7 @@ class ModelScanner {
|
||||||
private Set<Class<? extends IBase>> myVersionTypes;
|
private Set<Class<? extends IBase>> myVersionTypes;
|
||||||
|
|
||||||
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
|
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;
|
myContext = theContext;
|
||||||
myVersion = theVersion;
|
myVersion = theVersion;
|
||||||
Set<Class<? extends IBase>> toScan;
|
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
|
* 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) {
|
if (order == Child.ORDER_UNKNOWN) {
|
||||||
order = Integer.MIN_VALUE;
|
order = Integer.valueOf(0);
|
||||||
while (orderMap.containsKey(order)) {
|
while (orderMap.containsKey(order)) {
|
||||||
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.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
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.rest.server.AddProfileTagEnum;
|
||||||
import ca.uhn.fhir.util.ElementUtil;
|
import ca.uhn.fhir.util.ElementUtil;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
@ -42,6 +43,60 @@ public class CustomTypeDstu2Test {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
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
|
@Before
|
||||||
public void 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 java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
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.Medication;
|
||||||
import org.hl7.fhir.dstu3.model.MedicationOrder;
|
import org.hl7.fhir.dstu3.model.MedicationOrder;
|
||||||
import org.hl7.fhir.dstu3.model.Patient;
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
import org.hl7.fhir.dstu3.model.Quantity;
|
import org.hl7.fhir.dstu3.model.Quantity;
|
||||||
import org.hl7.fhir.dstu3.model.StringType;
|
import org.hl7.fhir.dstu3.model.StringType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
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.api.annotation.ResourceDef;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
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.rest.server.AddProfileTagEnum;
|
||||||
import ca.uhn.fhir.util.ElementUtil;
|
import ca.uhn.fhir.util.ElementUtil;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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
|
@Test
|
||||||
public void parseBundleWithResourceDirective() {
|
public void parseBundleWithResourceDirective() {
|
||||||
|
|
|
@ -208,6 +208,9 @@
|
||||||
(e.g. Patient with an active value of "1") the server should return an HTTP 400, not
|
(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!
|
an HTTP 500. Thanks to Jim Steel for reporting!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix" issue="364">
|
||||||
|
Enable parsers to parse and serialize custom resources that contain custom datatypes
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.5" date="2016-04-20">
|
<release version="1.5" date="2016-04-20">
|
||||||
<action type="fix" issue="339">
|
<action type="fix" issue="339">
|
||||||
|
|
Loading…
Reference in New Issue