From 20a705929c2acefb4c7a123735d4a5f0d342b196 Mon Sep 17 00:00:00 2001 From: jamesagnew <jamesagnew@gmail.com> Date: Sat, 22 Feb 2014 15:33:02 -0500 Subject: [PATCH] Starting on extensions --- .../BaseRuntimeChildDatatypeDefinition.java | 2 +- .../context/BaseRuntimeChildDefinition.java | 186 +--------------- .../context/BaseRuntimeElementDefinition.java | 2 +- .../BaseRuntimeUndeclaredChildDefinition.java | 186 ++++++++++++++++ .../java/ca/uhn/fhir/context/FhirContext.java | 8 +- .../ca/uhn/fhir/context/ModelScanner.java | 154 +++++++------ .../context/RuntimeChildChoiceDefinition.java | 2 +- .../RuntimeChildResourceBlockDefinition.java | 2 +- .../RuntimeChildResourceDefinition.java | 4 +- ...imeChildUndeclaredExtensionDefinition.java | 98 +++++++++ .../RuntimePrimitiveDatatypeDefinition.java | 5 +- ...ePrimitiveDatatypeNarrativeDefinition.java | 14 +- .../RuntimeResourceReferenceDefinition.java | 5 +- .../ca/uhn/fhir/model/api/BaseElement.java | 17 +- .../ca/uhn/fhir/model/api/IExtension.java | 5 + .../fhir/model/api/IPrimitiveDatatype.java | 2 +- .../api/ISupportsUndeclaredExtensions.java | 12 + .../fhir/model/api/UndeclaredExtension.java | 24 ++ .../fhir/model/api/annotation/Extension.java | 22 ++ .../model/api/annotation/ExtensionBlock.java | 7 + .../ca/uhn/fhir/model/datatype/AddressDt.java | 11 + .../fhir/model/datatype/BaseDateTimeDt.java | 4 +- .../ca/uhn/fhir/model/datatype/CodeDt.java | 4 +- .../ca/uhn/fhir/model/datatype/ContactDt.java | 11 + .../uhn/fhir/model/datatype/HumanNameDt.java | 11 + .../fhir/model/datatype/ICodedDatatype.java | 2 +- .../uhn/fhir/model/datatype/NarrativeDt.java | 11 + .../uhn/fhir/model/datatype/QuantityDt.java | 11 + .../ca/uhn/fhir/model/datatype/XhtmlDt.java | 76 ++++++- .../uhn/fhir/model/resource/Observation.java | 11 + .../ca/uhn/fhir/model/resource/Patient.java | 45 ++++ .../java/ca/uhn/fhir/parser/ParserState.java | 201 ++++++++++++++--- .../java/ca/uhn/fhir/parser/XmlParser.java | 206 +++++++++++++++--- .../ca/uhn/fhir/parser/XmlParserTest.java | 1 + .../resources/observation-example-eeg.xml | 80 ++++--- .../java/ca/uhn/fhir/starter/BaseParser.java | 144 ++++++------ .../ca/uhn/fhir/starter/ResourceParser.java | 78 ++++--- .../ca/uhn/fhir/starter/model/Extension.java | 84 +++++++ .../src/main/resources/dt_composite.vm | 3 + .../src/main/resources/resource.vm | 3 + .../src/main/resources/templates.vm | 59 +++++ 41 files changed, 1333 insertions(+), 480 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeUndeclaredChildDefinition.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IExtension.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/UndeclaredExtension.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Extension.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ExtensionBlock.java create mode 100644 hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/model/Extension.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java index 0ad65bf4253..c53389e6b1b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDatatypeDefinition.java @@ -9,7 +9,7 @@ import ca.uhn.fhir.model.api.ICodeEnum; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; -public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeChildDefinition { +public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeUndeclaredChildDefinition { private Class<? extends ICodeEnum> myCodeType; private Class<? extends IDatatype> myDatatype; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java index 16cc8f29dd7..73b4afaaff9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java @@ -1,204 +1,32 @@ package ca.uhn.fhir.context; -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; -import ca.uhn.fhir.util.BeanUtils; public abstract class BaseRuntimeChildDefinition { - private final IAccessor myAccessor; - private final String myElementName; - private final Field myField; - private final int myMax; - private final int myMin; - private final IMutator myMutator; - - BaseRuntimeChildDefinition(Field theField, int theMin, int theMax, String theElementName) throws ConfigurationException { - super(); - if (theField == null) { - throw new IllegalArgumentException("No field speficied"); - } - if (theMin < 0) { - throw new ConfigurationException("Min must be >= 0"); - } - if (theMax != -1 && theMax < theMin) { - throw new ConfigurationException("Max must be >= Min (unless it is -1 / unlimited)"); - } - if (isBlank(theElementName)) { - throw new ConfigurationException("Element name must not be blank"); - } - - myField = theField; - myMin = theMin; - myMax = theMax; - myElementName = theElementName; - - // TODO: handle lists (max>0), and maybe max=0? - - Class<?> declaringClass = myField.getDeclaringClass(); - final Class<?> targetReturnType = myField.getType(); - try { - final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, myElementName); - final Method mutator = BeanUtils.findMutator(declaringClass, targetReturnType, myElementName); - - if (List.class.isAssignableFrom(targetReturnType)) { - myAccessor = new ListAccessor(accessor); - myMutator = new ListMutator(mutator); - }else { - myAccessor = new PlainAccessor(accessor); - myMutator = new PlainMutator(targetReturnType, mutator); - } - } catch (NoSuchFieldException e) { - throw new ConfigurationException(e); - } - - } - - public IAccessor getAccessor() { - return myAccessor; - } + public abstract IAccessor getAccessor(); public abstract BaseRuntimeElementDefinition<?> getChildByName(String theName); - public String getElementName() { - return myElementName; - } - - public Field getField() { - return myField; - } - - public int getMax() { - return myMax; - } - - public int getMin() { - return myMin; - } - - public IMutator getMutator() { - return myMutator; - } - - public abstract Set<String> getValidChildNames(); + public abstract BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theType); public abstract String getChildNameByDatatype(Class<? extends IElement> theDatatype); - public abstract BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theType); + public abstract IMutator getMutator(); + + public abstract Set<String> getValidChildNames(); abstract void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions); - private final class ListMutator implements IMutator { - private final Method myMutator; - - private ListMutator(Method theMutator) { - myMutator = theMutator; - } - - @Override - public void addValue(Object theTarget, IElement theValue) { - List<IElement> existingList = myAccessor.getValues(theTarget); - if (existingList == null) { - existingList = new ArrayList<IElement>(); - try { - myMutator.invoke(theTarget, existingList); - } catch (IllegalAccessException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (IllegalArgumentException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (InvocationTargetException e) { - throw new ConfigurationException("Failed to get value", e); - } - } - existingList.add(theValue); - } - } - - private final class ListAccessor implements IAccessor { - private final Method myAccessor; - - private ListAccessor(Method theAccessor) { - myAccessor = theAccessor; - } - - @SuppressWarnings("unchecked") - @Override - public List<IElement> getValues(Object theTarget) { - try { - return (List<IElement>) myAccessor.invoke(theTarget); - } catch (IllegalAccessException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (IllegalArgumentException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (InvocationTargetException e) { - throw new ConfigurationException("Failed to get value", e); - } - } - } - - private final class PlainMutator implements IMutator { - private final Class<?> myTargetReturnType; - private final Method myMutator; - - private PlainMutator(Class<?> theTargetReturnType, Method theMutator) { - myTargetReturnType = theTargetReturnType; - myMutator = theMutator; - } - - @Override - public void addValue(Object theTarget, IElement theValue) { - try { - if (theValue != null && !myTargetReturnType.isAssignableFrom(theValue.getClass())) { - throw new ConfigurationException("Value for field " + myElementName + " expects type " + myTargetReturnType + " but got " + theValue.getClass()); - } - myMutator.invoke(theTarget, theValue); - } catch (IllegalAccessException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (IllegalArgumentException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (InvocationTargetException e) { - throw new ConfigurationException("Failed to get value", e); - } - } - } - - private final class PlainAccessor implements IAccessor { - private final Method myAccessor; - - private PlainAccessor(Method theAccessor) { - myAccessor = theAccessor; - } - - @Override - public List<IElement> getValues(Object theTarget) { - try { - return Collections.singletonList((IElement)myAccessor.invoke(theTarget)); - } catch (IllegalAccessException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (IllegalArgumentException e) { - throw new ConfigurationException("Failed to get value", e); - } catch (InvocationTargetException e) { - throw new ConfigurationException("Failed to get value", e); - } - } + public interface IAccessor { + List<? extends IElement> getValues(Object theTarget); } public interface IMutator { void addValue(Object theTarget, IElement theValue); } - - public interface IAccessor { - List<IElement> getValues(Object theTarget); - } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java index dc023002635..8a69c6ced86 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java @@ -49,7 +49,7 @@ public abstract class BaseRuntimeElementDefinition<T extends IElement> { public abstract ChildTypeEnum getChildType(); public enum ChildTypeEnum { - COMPOSITE_DATATYPE, PRIMITIVE_DATATYPE, RESOURCE, RESOURCE_REF, RESOURCE_BLOCK, PRIMITIVE_XHTML + COMPOSITE_DATATYPE, PRIMITIVE_DATATYPE, RESOURCE, RESOURCE_REF, RESOURCE_BLOCK, PRIMITIVE_XHTML, UNDECL_EXT } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeUndeclaredChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeUndeclaredChildDefinition.java new file mode 100644 index 00000000000..e01d237b52d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeUndeclaredChildDefinition.java @@ -0,0 +1,186 @@ +package ca.uhn.fhir.context; + +import static org.apache.commons.lang3.StringUtils.*; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.util.BeanUtils; + +public abstract class BaseRuntimeUndeclaredChildDefinition extends BaseRuntimeChildDefinition { + + private final IAccessor myAccessor; + private final String myElementName; + private final Field myField; + private final int myMax; + private final int myMin; + private final IMutator myMutator; + + BaseRuntimeUndeclaredChildDefinition(Field theField, int theMin, int theMax, String theElementName) throws ConfigurationException { + super(); + if (theField == null) { + throw new IllegalArgumentException("No field speficied"); + } + if (theMin < 0) { + throw new ConfigurationException("Min must be >= 0"); + } + if (theMax != -1 && theMax < theMin) { + throw new ConfigurationException("Max must be >= Min (unless it is -1 / unlimited)"); + } + if (isBlank(theElementName)) { + throw new ConfigurationException("Element name must not be blank"); + } + + myField = theField; + myMin = theMin; + myMax = theMax; + myElementName = theElementName; + + // TODO: handle lists (max>0), and maybe max=0? + + Class<?> declaringClass = myField.getDeclaringClass(); + final Class<?> targetReturnType = myField.getType(); + try { + final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, myElementName); + final Method mutator = BeanUtils.findMutator(declaringClass, targetReturnType, myElementName); + + if (List.class.isAssignableFrom(targetReturnType)) { + myAccessor = new ListAccessor(accessor); + myMutator = new ListMutator(mutator); + }else { + myAccessor = new PlainAccessor(accessor); + myMutator = new PlainMutator(targetReturnType, mutator); + } + } catch (NoSuchFieldException e) { + throw new ConfigurationException(e); + } + + } + + public IAccessor getAccessor() { + return myAccessor; + } + + public String getElementName() { + return myElementName; + } + + public Field getField() { + return myField; + } + + public int getMax() { + return myMax; + } + + public int getMin() { + return myMin; + } + + public IMutator getMutator() { + return myMutator; + } + + private final class ListMutator implements IMutator { + private final Method myMutator; + + private ListMutator(Method theMutator) { + myMutator = theMutator; + } + + @Override + public void addValue(Object theTarget, IElement theValue) { + @SuppressWarnings("unchecked") + List<IElement> existingList = (List<IElement>) myAccessor.getValues(theTarget); + if (existingList == null) { + existingList = new ArrayList<IElement>(); + try { + myMutator.invoke(theTarget, existingList); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + existingList.add(theValue); + } + } + + private final class ListAccessor implements IAccessor { + private final Method myAccessor; + + private ListAccessor(Method theAccessor) { + myAccessor = theAccessor; + } + + @SuppressWarnings("unchecked") + @Override + public List<IElement> getValues(Object theTarget) { + try { + return (List<IElement>) myAccessor.invoke(theTarget); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + } + + private final class PlainMutator implements IMutator { + private final Class<?> myTargetReturnType; + private final Method myMutator; + + private PlainMutator(Class<?> theTargetReturnType, Method theMutator) { + myTargetReturnType = theTargetReturnType; + myMutator = theMutator; + } + + @Override + public void addValue(Object theTarget, IElement theValue) { + try { + if (theValue != null && !myTargetReturnType.isAssignableFrom(theValue.getClass())) { + throw new ConfigurationException("Value for field " + myElementName + " expects type " + myTargetReturnType + " but got " + theValue.getClass()); + } + myMutator.invoke(theTarget, theValue); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + } + + private final class PlainAccessor implements IAccessor { + private final Method myAccessor; + + private PlainAccessor(Method theAccessor) { + myAccessor = theAccessor; + } + + @Override + public List<IElement> getValues(Object theTarget) { + try { + return Collections.singletonList((IElement)myAccessor.invoke(theTarget)); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index f9e71c0e084..d3ca31e6328 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -10,11 +10,17 @@ public class FhirContext { private final Map<String, RuntimeResourceDefinition> myNameToElementDefinition; private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition; + private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; public FhirContext(Class<? extends IResource>... theResourceTypes) { ModelScanner scanner = new ModelScanner(theResourceTypes); myNameToElementDefinition = Collections.unmodifiableMap(scanner.getNameToResourceDefinitions()); - myClassToElementDefinition = scanner.getClassToElementDefinitions(); + myClassToElementDefinition = Collections.unmodifiableMap(scanner.getClassToElementDefinitions()); + myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition(); + } + + public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { + return myRuntimeChildUndeclaredExtensionDefinition; } public Map<String, RuntimeResourceDefinition> getNameToResourceDefinition() { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index b7972a334b1..7829a3a0233 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.context; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; @@ -34,6 +34,8 @@ import ca.uhn.fhir.model.api.annotation.CodeTableDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.Narrative; import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.model.datatype.CodeDt; +import ca.uhn.fhir.model.datatype.DateDt; import ca.uhn.fhir.model.datatype.ICodedDatatype; import ca.uhn.fhir.model.datatype.NarrativeDt; import ca.uhn.fhir.model.datatype.XhtmlDt; @@ -42,22 +44,24 @@ class ModelScanner { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>(); + private Map<String, BaseRuntimeElementDefinition<?>> myDatatypeAttributeNameToDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>(); private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>(); - private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>(); // private Map<String, RuntimeResourceDefinition> // myNameToDatatypeDefinitions = new HashMap<String, // RuntimeDatatypeDefinition>(); - public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() { - return (myNameToResourceDefinitions); - } + private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>(); + + private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; ModelScanner(Class<? extends IResource>... theResourceTypes) throws ConfigurationException { Set<Class<? extends IElement>> toScan = new HashSet<Class<? extends IElement>>(Arrays.asList(theResourceTypes)); toScan.add(NarrativeDt.class); + toScan.add(DateDt.class); + toScan.add(CodeDt.class); do { for (Class<? extends IElement> nextClass : toScan) { @@ -77,10 +81,41 @@ class ModelScanner { next.sealAndInitialize(myClassToElementDefinitions); } + myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition(myDatatypeAttributeNameToDefinition); + ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size()); } + public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { + return myRuntimeChildUndeclaredExtensionDefinition; + } + + public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() { + return myClassToElementDefinitions; + } + + public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() { + return (myNameToResourceDefinitions); + } + + private void addDatatype(BaseRuntimeElementDefinition<?> theResourceDef) { + String attrName = theResourceDef.getName(); + attrName = "value" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1); + myDatatypeAttributeNameToDefinition.put(attrName, theResourceDef); + } + + private void addScanAlso(Class<? extends IElement> theType) { + if (theType.isInterface()) { + return; + } + myScanAlso.add(theType); + } + + public Map<String, BaseRuntimeElementDefinition<?>> getDatatypeAttributeNameToDefinition() { + return myDatatypeAttributeNameToDefinition; + } + private void scan(Class<? extends IElement> theClass) throws ConfigurationException { BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass); if (existingDef != null) { @@ -153,6 +188,10 @@ class ModelScanner { scanCompositeElementForChildren(theClass, resourceDef, null); } + private String scanCodeTable(Class<? extends ICodeEnum> theCodeType, CodeTableDef theCodeTableDefinition) { + return null; // TODO: implement + } + private void scanCompositeDatatype(Class<? extends ICompositeDatatype> theClass, DatatypeDef theDatatypeDefinition) { ourLog.debug("Scanning resource class: {}", theClass.getName()); @@ -163,57 +202,15 @@ class ModelScanner { RuntimeCompositeDatatypeDefinition resourceDef = new RuntimeCompositeDatatypeDefinition(resourceName, theClass); myClassToElementDefinitions.put(theClass, resourceDef); + addDatatype(resourceDef); scanCompositeElementForChildren(theClass, resourceDef, null); } - private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype<?>> theClass, DatatypeDef theDatatypeDefinition) { - ourLog.debug("Scanning resource class: {}", theClass.getName()); - - String resourceName = theDatatypeDefinition.name(); - if (isBlank(resourceName)) { - throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName()); - } - - RuntimePrimitiveDatatypeDefinition resourceDef; - if (theClass.equals(XhtmlDt.class)) { - resourceDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, theClass); - } else { - resourceDef = new RuntimePrimitiveDatatypeDefinition(resourceName, theClass); - } - myClassToElementDefinitions.put(theClass, resourceDef); - - return resourceName; - } - - private String scanResource(Class<? extends IResource> theClass, ResourceDef resourceDefinition) { - ourLog.debug("Scanning resource class: {}", theClass.getName()); - - String resourceName = resourceDefinition.name(); - if (isBlank(resourceName)) { - throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName()); - } - - if (myNameToResourceDefinitions.containsKey(resourceName)) { - if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) { - throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); - } - return resourceName; - } - - RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceName); - myClassToElementDefinitions.put(theClass, resourceDef); - myNameToResourceDefinitions.put(resourceName, resourceDef); - - scanCompositeElementForChildren(theClass, resourceDef, resourceDefinition.identifierOrder()); - - return resourceName; - } - @SuppressWarnings("unchecked") private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition, Integer theIdentifierOrder) { Set<String> elementNames = new HashSet<String>(); - TreeMap<Integer, BaseRuntimeChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeChildDefinition>(); + TreeMap<Integer, BaseRuntimeUndeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeUndeclaredChildDefinition>(); LinkedList<Class<? extends ICompositeElement>> classes = new LinkedList<Class<? extends ICompositeElement>>(); Class<? extends ICompositeElement> current = theClass; @@ -231,7 +228,7 @@ class ModelScanner { } while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() < 0) { - BaseRuntimeChildDefinition elementDef = orderToElementDef.remove(orderToElementDef.firstKey()); + BaseRuntimeUndeclaredChildDefinition elementDef = orderToElementDef.remove(orderToElementDef.firstKey()); if (elementDef.getElementName().equals("identifier")) { orderToElementDef.put(theIdentifierOrder, elementDef); } else { @@ -250,7 +247,7 @@ class ModelScanner { } @SuppressWarnings("unchecked") - private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition, Set<String> elementNames, TreeMap<Integer, BaseRuntimeChildDefinition> orderToElementDef) { + private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition, Set<String> elementNames, TreeMap<Integer, BaseRuntimeUndeclaredChildDefinition> orderToElementDef) { for (Field next : theClass.getDeclaredFields()) { Narrative hasNarrative = next.getAnnotation(Narrative.class); @@ -377,11 +374,50 @@ class ModelScanner { } } - private void addScanAlso(Class<? extends IElement> theType) { - if (theType.isInterface()) { - return; + private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype<?>> theClass, DatatypeDef theDatatypeDefinition) { + ourLog.debug("Scanning resource class: {}", theClass.getName()); + + String resourceName = theDatatypeDefinition.name(); + if (isBlank(resourceName)) { + throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName()); } - myScanAlso.add(theType); + + BaseRuntimeElementDefinition<?> resourceDef; + if (theClass.equals(XhtmlDt.class)) { + @SuppressWarnings("unchecked") + Class<XhtmlDt> clazz = (Class<XhtmlDt>) theClass; + resourceDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz); + } else { + resourceDef = new RuntimePrimitiveDatatypeDefinition(resourceName, theClass); + } + myClassToElementDefinitions.put(theClass, resourceDef); + addDatatype(resourceDef); + + return resourceName; + } + + private String scanResource(Class<? extends IResource> theClass, ResourceDef resourceDefinition) { + ourLog.debug("Scanning resource class: {}", theClass.getName()); + + String resourceName = resourceDefinition.name(); + if (isBlank(resourceName)) { + throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName()); + } + + if (myNameToResourceDefinitions.containsKey(resourceName)) { + if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) { + throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); + } + return resourceName; + } + + RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceName); + myClassToElementDefinitions.put(theClass, resourceDef); + myNameToResourceDefinitions.put(resourceName, resourceDef); + + scanCompositeElementForChildren(theClass, resourceDef, resourceDefinition.identifierOrder()); + + return resourceName; } private static Class<?> getGenericCollectionTypeOfField(Field next) { @@ -398,12 +434,4 @@ class ModelScanner { return type; } - private String scanCodeTable(Class<? extends ICodeEnum> theCodeType, CodeTableDef theCodeTableDefinition) { - return null; // TODO: implement - } - - public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() { - return myClassToElementDefinitions; - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java index 45e4ae77c0d..61a4212ef92 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java @@ -10,7 +10,7 @@ import java.util.Set; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; -public class RuntimeChildChoiceDefinition extends BaseRuntimeChildDefinition { +public class RuntimeChildChoiceDefinition extends BaseRuntimeUndeclaredChildDefinition { private List<Class<? extends IElement>> myChoiceTypes; private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java index a492f62296a..45895587413 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceBlockDefinition.java @@ -9,7 +9,7 @@ import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IResourceBlock; -public class RuntimeChildResourceBlockDefinition extends BaseRuntimeChildDefinition { +public class RuntimeChildResourceBlockDefinition extends BaseRuntimeUndeclaredChildDefinition { private RuntimeResourceBlockDefinition myElementDef; private Class<? extends IResourceBlock> myResourceBlockType; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceDefinition.java index ce0a37930ec..aa7dec17dce 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildResourceDefinition.java @@ -11,7 +11,7 @@ import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceReference; -public class RuntimeChildResourceDefinition extends BaseRuntimeChildDefinition { +public class RuntimeChildResourceDefinition extends BaseRuntimeUndeclaredChildDefinition { private BaseRuntimeElementDefinition<?> myRuntimeDef; private List<Class<? extends IResource>> myResourceTypes; @@ -49,6 +49,6 @@ public class RuntimeChildResourceDefinition extends BaseRuntimeChildDefinition { @Override void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { - myRuntimeDef = new RuntimeResourceReferenceDefinition(getElementName(), myResourceTypes); + myRuntimeDef = new RuntimeResourceReferenceDefinition(getElementName()); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java new file mode 100644 index 00000000000..a615148aaa5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java @@ -0,0 +1,98 @@ +package ca.uhn.fhir.context; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import ca.uhn.fhir.model.api.IDatatype; +import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.model.api.ResourceReference; +import ca.uhn.fhir.model.api.UndeclaredExtension; + +public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildDefinition { + + + private Map<String, BaseRuntimeElementDefinition<?>> myAttributeNameToDefinition; + private Map<Class<? extends IElement>,String> myDatatypeToAttributeName; + private Map<Class<? extends IElement>,BaseRuntimeElementDefinition<?>> myDatatypeToDefinition; + + public RuntimeChildUndeclaredExtensionDefinition(Map<String, BaseRuntimeElementDefinition<?>> theDatatypeAttributeNameToDefinition) { + myAttributeNameToDefinition=theDatatypeAttributeNameToDefinition; + + myDatatypeToAttributeName = new HashMap<Class<? extends IElement>, String>(); + myDatatypeToDefinition = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>(); + + for (Entry<String, BaseRuntimeElementDefinition<?>> next : myAttributeNameToDefinition.entrySet()) { + @SuppressWarnings("unchecked") + Class<? extends IDatatype> type = (Class<? extends IDatatype>) next.getValue().getImplementingClass(); + myDatatypeToAttributeName.put(type, next.getKey()); + myDatatypeToDefinition.put(type, next.getValue()); + } + + // Resource Reference + myDatatypeToAttributeName.put(ResourceReference.class, "valueReference"); + RuntimeResourceReferenceDefinition def = new RuntimeResourceReferenceDefinition("valueResource"); + myAttributeNameToDefinition.put("valueResource", def); + myDatatypeToDefinition.put(ResourceReference.class, def); + + } + + + @Override + public BaseRuntimeElementDefinition<?> getChildByName(String theName) { + return myAttributeNameToDefinition.get(theName); + } + + @Override + public Set<String> getValidChildNames() { + return myAttributeNameToDefinition.keySet(); + } + + @Override + public String getChildNameByDatatype(Class<? extends IElement> theDatatype) { + return myDatatypeToAttributeName.get(theDatatype); + } + + @Override + public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theType) { + return myDatatypeToDefinition.get(theType); + } + + @Override + void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { + // nothing + } + + + @Override + public IAccessor getAccessor() { + return new IAccessor() { + @Override + public List<? extends IElement> getValues(Object theTarget) { + UndeclaredExtension target = (UndeclaredExtension)theTarget; + if (target.getValue() != null) { + return Collections.singletonList(target.getValue()); + } + return target.getUndeclaredExtensions(); + }}; + } + + + @Override + public IMutator getMutator() { + return new IMutator() { + @Override + public void addValue(Object theTarget, IElement theValue) { + UndeclaredExtension target = (UndeclaredExtension)theTarget; + if (theValue instanceof IDatatype) { + target.setValue((IDatatype) theTarget); + }else { + target.getUndeclaredExtensions().add((UndeclaredExtension) theValue); + } + }}; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeDefinition.java index 0e67400bfce..3801aeb525b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeDefinition.java @@ -2,13 +2,12 @@ package ca.uhn.fhir.context; import java.util.Map; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IPrimitiveDatatype; -public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefinition<IPrimitiveDatatype>{ +public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefinition<IPrimitiveDatatype<?>>{ - public RuntimePrimitiveDatatypeDefinition(String theName, Class<? extends IPrimitiveDatatype> theImplementingClass) { + public RuntimePrimitiveDatatypeDefinition(String theName, Class<? extends IPrimitiveDatatype<?>> theImplementingClass) { super(theName, theImplementingClass); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeNarrativeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeNarrativeDefinition.java index e6bbd7ffb6e..39e34fdbb20 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeNarrativeDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimePrimitiveDatatypeNarrativeDefinition.java @@ -1,10 +1,13 @@ package ca.uhn.fhir.context; -import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import java.util.Map; -public class RuntimePrimitiveDatatypeNarrativeDefinition extends RuntimePrimitiveDatatypeDefinition { +import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.model.datatype.XhtmlDt; - public RuntimePrimitiveDatatypeNarrativeDefinition(String theName, Class<? extends IPrimitiveDatatype<?>> theImplementingClass) { +public class RuntimePrimitiveDatatypeNarrativeDefinition extends BaseRuntimeElementDefinition<XhtmlDt> { + + public RuntimePrimitiveDatatypeNarrativeDefinition(String theName, Class<XhtmlDt> theImplementingClass) { super(theName, theImplementingClass); } @@ -13,4 +16,9 @@ public class RuntimePrimitiveDatatypeNarrativeDefinition extends RuntimePrimitiv return ChildTypeEnum.PRIMITIVE_XHTML; } + @Override + void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { + // nothing + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceReferenceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceReferenceDefinition.java index 081547ca214..208689e05c3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceReferenceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceReferenceDefinition.java @@ -1,16 +1,13 @@ package ca.uhn.fhir.context; -import java.util.List; import java.util.Map; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; import ca.uhn.fhir.model.api.IElement; -import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceReference; public class RuntimeResourceReferenceDefinition extends BaseRuntimeElementDefinition<ResourceReference> { - public RuntimeResourceReferenceDefinition(String theName, List<Class<? extends IResource>> theResourceTypes) { + public RuntimeResourceReferenceDefinition(String theName) { super(theName, ResourceReference.class); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseElement.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseElement.java index c3a67104a00..3623be9cba3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseElement.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseElement.java @@ -1,5 +1,20 @@ package ca.uhn.fhir.model.api; -public abstract class BaseElement implements IElement { +import java.util.ArrayList; +import java.util.List; +public abstract class BaseElement implements IElement, ISupportsUndeclaredExtensions { + + private List<UndeclaredExtension> myUndeclaredExtensions; + + @Override + public List<UndeclaredExtension> getUndeclaredExtensions() { + if (myUndeclaredExtensions==null) { + myUndeclaredExtensions=new ArrayList<UndeclaredExtension>(); + } + return myUndeclaredExtensions; + } + + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IExtension.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IExtension.java new file mode 100644 index 00000000000..2573fcdcbdb --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IExtension.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.model.api; + +public interface IExtension extends IElement { + // nothing +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java index a2a4dc8db4c..da040174bce 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java @@ -6,7 +6,7 @@ public interface IPrimitiveDatatype<T> extends IDatatype { void setValueAsString(String theValue) throws DataFormatException; - String getValueAsString(); + String getValueAsString() throws DataFormatException; T getValue(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java new file mode 100644 index 00000000000..65183d43475 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java @@ -0,0 +1,12 @@ +package ca.uhn.fhir.model.api; + +import java.util.List; + +public interface ISupportsUndeclaredExtensions { + + /** + * Returns a list containing all undeclared extensions + */ + List<UndeclaredExtension> getUndeclaredExtensions(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/UndeclaredExtension.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/UndeclaredExtension.java new file mode 100644 index 00000000000..315c1c721bb --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/UndeclaredExtension.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.model.api; + +public class UndeclaredExtension extends BaseElement { + + private String myUrl; + private IElement myValue; + + public String getUrl() { + return myUrl; + } + + public IElement getValue() { + return myValue; + } + + public void setUrl(String theUrl) { + myUrl = theUrl; + } + + public void setValue(IElement theValue) { + myValue = theValue; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Extension.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Extension.java new file mode 100644 index 00000000000..bb31dd848d5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Extension.java @@ -0,0 +1,22 @@ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +import ca.uhn.fhir.model.api.IDatatype; + +@Target(value= {ElementType.FIELD}) +public @interface Extension { + + String url(); + + Class<? extends IDatatype> datatype() default NoDatatype.class; + + public static class NoDatatype implements IDatatype + { + private NoDatatype() { + // non-instantiable + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ExtensionBlock.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ExtensionBlock.java new file mode 100644 index 00000000000..1dda0872e94 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ExtensionBlock.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.model.api.annotation; + +public @interface ExtensionBlock { + + String url(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/AddressDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/AddressDt.java index 7fcb76823e5..aa0b10d5957 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/AddressDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/AddressDt.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.datatype; import java.util.*; @@ -243,4 +253,5 @@ P.O. Box number, delivery hints, and similar address information } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java index 05b8a37e974..58a1db13a4b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java @@ -17,7 +17,7 @@ public abstract class BaseDateTimeDt extends BasePrimitiveDatatype<Date> { private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd"); private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM"); private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); - private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssX"); + private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ"); private int myPrecision = Calendar.SECOND; private Date myValue; @@ -114,7 +114,7 @@ public abstract class BaseDateTimeDt extends BasePrimitiveDatatype<Date> { setValue(ourYearMonthFormat.parse(theValue)); setPrecision(Calendar.MONTH); clearTimeZone(); - } else if (theValue.length() == 9 && isPrecisionAllowed(Calendar.DAY_OF_MONTH)) { + } else if (theValue.length() == 10 && isPrecisionAllowed(Calendar.DAY_OF_MONTH)) { setValue(ourYearMonthDayFormat.parse(theValue)); setPrecision(Calendar.DAY_OF_MONTH); clearTimeZone(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java index c205ba46a65..52792d6615c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java @@ -1,12 +1,12 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; -import ca.uhn.fhir.model.api.ICodeEnum; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.parser.DataFormatException; @DatatypeDef(name = "code") -public class CodeDt extends BasePrimitiveDatatype<String> implements ICodedDatatype { +public class CodeDt extends BasePrimitiveDatatype<String> implements ICodedDatatype, IPrimitiveDatatype<String> { private String myValue; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ContactDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ContactDt.java index 118a05406aa..5f3bb1981e2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ContactDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ContactDt.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.datatype; import java.util.*; @@ -133,4 +143,5 @@ public class ContactDt extends BaseCompositeDatatype { } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/HumanNameDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/HumanNameDt.java index e72f24412b7..c2b2234f71e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/HumanNameDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/HumanNameDt.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.datatype; import java.util.*; @@ -214,4 +224,5 @@ public class HumanNameDt extends BaseCompositeDatatype { } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ICodedDatatype.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ICodedDatatype.java index 5bedb943da3..d36a61d13de 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ICodedDatatype.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/ICodedDatatype.java @@ -3,5 +3,5 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.IDatatype; public interface ICodedDatatype extends IDatatype { - + // nothing } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java index 92dbcde4388..41d53722318 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.datatype; import java.util.*; @@ -79,4 +89,5 @@ public class NarrativeDt extends BaseCompositeDatatype { } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/QuantityDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/QuantityDt.java index ac520f1d69d..f620dea1ecf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/QuantityDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/QuantityDt.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.datatype; import java.util.*; @@ -160,4 +170,5 @@ public class QuantityDt extends BaseCompositeDatatype { } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/XhtmlDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/XhtmlDt.java index 9ed8d622553..6212e2ed0ce 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/XhtmlDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/XhtmlDt.java @@ -1,32 +1,86 @@ package ca.uhn.fhir.model.datatype; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.parser.DataFormatException; @DatatypeDef(name = "xhtml") -public class XhtmlDt implements IPrimitiveDatatype<String> { +public class XhtmlDt implements IPrimitiveDatatype<List<XMLEvent>> { - private String myValue; + private List<XMLEvent> myValue; @Override public void setValueAsString(String theValue) throws DataFormatException { - myValue=theValue; + if (theValue == null) { + myValue = null; + return; + } + String val = "<a>" + theValue + "</a>"; + try { + ArrayList<XMLEvent> value = new ArrayList<XMLEvent>(); + XMLEventReader er = XMLInputFactory.newInstance().createXMLEventReader(new StringReader(val)); + boolean first = true; + while (er.hasNext()) { + if (first) { + first = false; + continue; + } + XMLEvent next = er.nextEvent(); + if (er.hasNext()) { + // don't add the last event + value.add(next); + } + } + + } catch (XMLStreamException e) { + throw new DataFormatException("String does not appear to be valid XML/XHTML", e); + } catch (FactoryConfigurationError e) { + throw new ConfigurationException(e); + } } @Override - public String getValueAsString() { + public String getValueAsString() throws DataFormatException { + if (myValue == null) { + return null; + } + try { + StringWriter w = new StringWriter(); + XMLEventWriter ew = XMLOutputFactory.newInstance().createXMLEventWriter(w); + for (XMLEvent next : myValue) { + ew.add(next); + } + ew.close(); + return w.toString(); + } catch (XMLStreamException e) { + throw new DataFormatException("Problem with the contained XML events", e); + } catch (FactoryConfigurationError e) { + throw new ConfigurationException(e); + } + } + + @Override + public List<XMLEvent> getValue() { return myValue; } @Override - public String getValue() { - return myValue; - } - - @Override - public void setValue(String theValue) throws DataFormatException { - myValue=theValue; + public void setValue(List<XMLEvent> theValue) throws DataFormatException { + myValue = theValue; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java index 5a95e4b1858..3b2adb6e4d0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.resource; import java.util.*; @@ -1421,4 +1431,5 @@ public class Observation extends BaseResource { } + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Patient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Patient.java index 0868e07c52e..53c52b432c3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Patient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Patient.java @@ -1,5 +1,15 @@ + + + + + + + + + + package ca.uhn.fhir.model.resource; import java.util.*; @@ -24,6 +34,10 @@ import ca.uhn.fhir.model.datatype.*; @ResourceDef(name="Patient") public class Patient extends BaseResource { + @Child() + private Foo1 myFoo1; + @Child() + private Bar1 myBar1; @Child(name="identifier", order=0, min=0, max=Child.MAX_UNLIMITED) private List<IdentifierDt> myIdentifier; @@ -1950,4 +1964,35 @@ public class Patient extends BaseResource { } + @ExtensionBlock(url="http://foo/1") + public class Foo1 implements IExtension { + + @Child(name="value", order=0) + private StringDt myValue; + + /** + * Gets the value + */ + public StringDt getValue() { + return myValue; + } + + /** + * Sets the value + */ + public void setValue(StringDt theValue) { + myValue = theValue; + } + + + } + + @ExtensionBlock(url="http://bar/1") + public class Bar1 implements IExtension { + + + + } + + } \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index c596ca7be66..3eac0df19c3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -1,11 +1,8 @@ package ca.uhn.fhir.parser; -import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLEventWriter; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; @@ -16,15 +13,20 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition; import ca.uhn.fhir.context.RuntimeResourceBlockDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceReferenceDefinition; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.ICompositeElement; +import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResourceBlock; import ca.uhn.fhir.model.api.ResourceReference; +import ca.uhn.fhir.model.api.UndeclaredExtension; +import ca.uhn.fhir.model.datatype.XhtmlDt; class ParserState { @@ -51,6 +53,10 @@ class ParserState { myState.enteringNewElement(theElement, theName); } + public void enteringNewElementExtension(StartElement theElem, String theUrlAttr) { + myState.enteringNewElementExtension(theElem, theUrlAttr); + } + public Object getObject() { return myObject; } @@ -59,6 +65,10 @@ class ParserState { return myObject != null; } + public void otherEvent(XMLEvent theEvent) throws DataFormatException { + myState.otherEvent(theEvent); + } + private void pop() { myState = myState.myStack; } @@ -96,11 +106,25 @@ class ParserState { public abstract void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException; + public void enteringNewElementExtension(@SuppressWarnings("unused") StartElement theElement, String theUrlAttr) { + // TODO: handle predefined extensions + + if (getCurrentElement() instanceof ISupportsUndeclaredExtensions) { + UndeclaredExtension newExtension = new UndeclaredExtension(); + newExtension.setUrl(theUrlAttr); + ((ISupportsUndeclaredExtensions) getCurrentElement()).getUndeclaredExtensions().add(newExtension); + ExtensionState newState = new ExtensionState(newExtension); + push(newState); + } + } + + public abstract void otherEvent(XMLEvent theEvent) throws DataFormatException; + public void setStack(BaseState theState) { myStack = theState; } - public abstract void otherEvent(XMLEvent theEvent); + protected abstract IElement getCurrentElement(); } @@ -165,18 +189,107 @@ class ParserState { push(newState); return; } + case PRIMITIVE_XHTML: { + RuntimePrimitiveDatatypeNarrativeDefinition xhtmlTarget = (RuntimePrimitiveDatatypeNarrativeDefinition) target; + XhtmlDt newDt = xhtmlTarget.newInstance(); + child.getMutator().addValue(myInstance, newDt); + XhtmlState state = new XhtmlState(newDt, theElement); + push(state); + return; + } + case UNDECL_EXT: case RESOURCE: { // Throw an exception because this shouldn't happen here break; - } - case PRIMITIVE_XHTML: { - } } throw new DataFormatException("Illegal resource position: " + target.getChildType()); } + @Override + public void otherEvent(XMLEvent theEvent) { + // ignore + } + + @Override + protected IElement getCurrentElement() { + return myInstance; + } + + } + + private class ExtensionState extends BaseState { + + private UndeclaredExtension myExtension; + + public ExtensionState(UndeclaredExtension theExtension) { + myExtension = theExtension; + } + + @Override + public void attributeValue(Attribute theAttribute, String theValue) throws DataFormatException { + throw new DataFormatException("'value' attribute is invalid in 'extension' element"); + } + + @Override + public void endingElement(EndElement theElem) throws DataFormatException { + if (myExtension.getValue() != null && myExtension.getUndeclaredExtensions().size() > 0) { + throw new DataFormatException("Extension must not have both a value and other contained extensions"); + } + pop(); + } + + @Override + public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException { + BaseRuntimeElementDefinition<?> target = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(theLocalPart); + if (target == null) { + throw new DataFormatException("Unknown extension element name: " + theLocalPart); + } + + switch (target.getChildType()) { + case COMPOSITE_DATATYPE: { + BaseRuntimeElementCompositeDefinition<?> compositeTarget = (BaseRuntimeElementCompositeDefinition<?>) target; + ICompositeDatatype newChildInstance = (ICompositeDatatype) compositeTarget.newInstance(); + myExtension.setValue(newChildInstance); + ContainerState newState = new ContainerState(compositeTarget, newChildInstance); + push(newState); + return; + } + case PRIMITIVE_DATATYPE: { + RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; + IPrimitiveDatatype<?> newChildInstance = primitiveTarget.newInstance(); + myExtension.setValue(newChildInstance); + PrimitiveState newState = new PrimitiveState(newChildInstance); + push(newState); + return; + } + case RESOURCE_REF: { + RuntimeResourceReferenceDefinition resourceRefTarget = (RuntimeResourceReferenceDefinition) target; + ResourceReference newChildInstance = new ResourceReference(); + myExtension.setValue(newChildInstance); + ResourceReferenceState newState = new ResourceReferenceState(resourceRefTarget, newChildInstance); + push(newState); + return; + } + case PRIMITIVE_XHTML: + case RESOURCE: + case RESOURCE_BLOCK: + case UNDECL_EXT: + break; + } + } + + @Override + public void otherEvent(XMLEvent theEvent) throws DataFormatException { + // ignore + } + + @Override + protected IElement getCurrentElement() { + return myExtension; + } + } private class PrimitiveState extends BaseState { @@ -202,6 +315,16 @@ class ParserState { throw new Error("?? can this happen?"); // TODO: can this happen? } + @Override + public void otherEvent(XMLEvent theEvent) { + // ignore + } + + @Override + protected IElement getCurrentElement() { + return myInstance; + } + } private class ResourceReferenceState extends BaseState { @@ -260,6 +383,16 @@ class ParserState { } } + @Override + public void otherEvent(XMLEvent theEvent) { + // ignore + } + + @Override + protected IElement getCurrentElement() { + return myInstance; + } + } private enum ResourceReferenceSubState { @@ -267,47 +400,47 @@ class ParserState { } private class XhtmlState extends BaseState { - private StringWriter myStringWriter; - private XMLEventWriter myEventWriter; - private XMLEventFactory myEventFactory; + private int myDepth; + private XhtmlDt myDt; + private List<XMLEvent> myEvents = new ArrayList<XMLEvent>(); - private XhtmlState() throws DataFormatException { - try { - XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance(); - myStringWriter = new StringWriter(); - myEventWriter = xmlFactory.createXMLEventWriter(myStringWriter); - } catch (XMLStreamException e) { - throw new DataFormatException(e); - } + private XhtmlState(XhtmlDt theXhtmlDt, StartElement theXhtmlStartElement) throws DataFormatException { + myDepth = 1; + myDt = theXhtmlDt; + myEvents.add(theXhtmlStartElement); } @Override public void attributeValue(Attribute theAttr, String theValue) throws DataFormatException { - try { - myEventWriter.add(theAttr); - } catch (XMLStreamException e) { - throw new DataFormatException(e); - } + myEvents.add(theAttr); } @Override public void endingElement(EndElement theElement) throws DataFormatException { - try { - myEventWriter.add(theElement); - } catch (XMLStreamException e) { - throw new DataFormatException(e); + myEvents.add(theElement); + + myDepth--; + if (myDepth == 0) { + myDt.setValue(myEvents); + pop(); } } @Override public void enteringNewElement(StartElement theElem, String theLocalPart) throws DataFormatException { - // TODO Auto-generated method stub + myDepth++; + myEvents.add(theElem); + } + @Override + public void otherEvent(XMLEvent theEvent) throws DataFormatException { + myEvents.add(theEvent); + } + + @Override + protected IElement getCurrentElement() { + return myDt; } } - public void otherEvent(XMLEvent theEvent) { - myState.otherEvent(theEvent); - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 2a4b71636f7..907f6c16195 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -1,21 +1,28 @@ package ca.uhn.fhir.parser; +import static org.apache.commons.lang3.StringUtils.*; + import java.io.StringReader; import java.io.StringWriter; import java.util.Iterator; import java.util.List; +import javax.xml.namespace.QName; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.Comment; import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.EntityReference; +import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; -import javax.xml.transform.OutputKeys; import org.apache.commons.lang3.StringUtils; @@ -24,14 +31,20 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.IExtension; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceReference; +import ca.uhn.fhir.model.api.UndeclaredExtension; +import ca.uhn.fhir.model.datatype.XhtmlDt; import ca.uhn.fhir.util.PrettyPrintWriterWrapper; public class XmlParser { + private static final String XHTML_NS = "http://www.w3.org/1999/xhtml"; private static final String FHIR_NS = "http://hl7.org/fhir"; @SuppressWarnings("unused") private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class); @@ -45,13 +58,13 @@ public class XmlParser { myXmlOutputFactory = XMLOutputFactory.newInstance(); } - public String encodeResourceToString(IResource theResource) { + public String encodeResourceToString(IResource theResource) throws DataFormatException { XMLStreamWriter eventWriter; StringWriter stringWriter = new StringWriter(); try { eventWriter = myXmlOutputFactory.createXMLStreamWriter(stringWriter); eventWriter = new PrettyPrintWriterWrapper(eventWriter); - + RuntimeResourceDefinition resDef = (RuntimeResourceDefinition) myContext.getClassToElementDefinition().get(theResource.getClass()); eventWriter.writeStartElement(resDef.getName()); eventWriter.writeDefaultNamespace(FHIR_NS); @@ -67,9 +80,10 @@ public class XmlParser { return stringWriter.toString(); } - private void encodeCompositeElementToStreamWriter(IElement theResource, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException { + private void encodeCompositeElementToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter,BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException, DataFormatException { + encodeExtensionsIfPresent(theEventWriter, theElement); for (BaseRuntimeChildDefinition nextChild : resDef.getChildren()) { - List<IElement> values = nextChild.getAccessor().getValues(theResource); + List<? extends IElement> values = nextChild.getAccessor().getValues(theElement); if (values == null || values.isEmpty()) { continue; } @@ -81,34 +95,146 @@ public class XmlParser { Class<? extends IElement> type = nextValue.getClass(); String childName = nextChild.getChildNameByDatatype(type); BaseRuntimeElementDefinition<?> childDef = nextChild.getChildElementDefinitionByDatatype(type); - theEventWriter.writeStartElement(childName); - switch (childDef.getChildType()) { - case PRIMITIVE_DATATYPE: { - IPrimitiveDatatype<?> pd = (IPrimitiveDatatype<?>) nextValue; - theEventWriter.writeAttribute("value", pd.getValueAsString()); - break; - } - case RESOURCE_BLOCK: - case COMPOSITE_DATATYPE: { - BaseRuntimeElementCompositeDefinition<?> childCompositeDef = (BaseRuntimeElementCompositeDefinition<?>) childDef; - encodeCompositeElementToStreamWriter(nextValue, theEventWriter, childCompositeDef); - break; - } - case RESOURCE_REF: { - ResourceReference ref = (ResourceReference) nextValue; - encodeResourceReferenceToStreamWriter(theEventWriter, ref); - break; - } - case RESOURCE: - throw new IllegalStateException(); // should not happen - } - - theEventWriter.writeEndElement(); + encodeChildElementToStreamWriter(theEventWriter, nextValue, childName, childDef); } } } + private void encodeChildElementToStreamWriter(XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef) throws XMLStreamException, DataFormatException { + switch (childDef.getChildType()) { + case PRIMITIVE_DATATYPE: { + theEventWriter.writeStartElement(childName); + IPrimitiveDatatype<?> pd = (IPrimitiveDatatype<?>) nextValue; + theEventWriter.writeAttribute("value", pd.getValueAsString()); + encodeExtensionsIfPresent(theEventWriter, nextValue); + theEventWriter.writeEndElement(); + break; + } + case RESOURCE_BLOCK: + case COMPOSITE_DATATYPE: { + theEventWriter.writeStartElement(childName); + BaseRuntimeElementCompositeDefinition<?> childCompositeDef = (BaseRuntimeElementCompositeDefinition<?>) childDef; + encodeCompositeElementToStreamWriter(nextValue, theEventWriter, childCompositeDef); + encodeExtensionsIfPresent(theEventWriter, nextValue); + theEventWriter.writeEndElement(); + break; + } + case RESOURCE_REF: { + theEventWriter.writeStartElement(childName); + ResourceReference ref = (ResourceReference) nextValue; + encodeResourceReferenceToStreamWriter(theEventWriter, ref); + theEventWriter.writeEndElement(); + break; + } + case RESOURCE: { + throw new IllegalStateException(); // should not happen + } + case PRIMITIVE_XHTML: { + XhtmlDt dt = (XhtmlDt) nextValue; + encodeXhtml(dt, theEventWriter); + break; + } + case UNDECL_EXT: { + throw new IllegalStateException("should not happen"); + } + } + } + + private void encodeExtensionsIfPresent(XMLStreamWriter theWriter, IElement theResource) throws XMLStreamException, DataFormatException { + if (theResource instanceof ISupportsUndeclaredExtensions) { + for (UndeclaredExtension next : ((ISupportsUndeclaredExtensions) theResource).getUndeclaredExtensions()) { + theWriter.writeStartElement("extension"); + theWriter.writeAttribute("url", next.getUrl()); + + if (next.getValue() != null) { + IElement nextValue = next.getValue(); + RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition(); + String childName = extDef.getChildNameByDatatype(nextValue.getClass()); + BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(nextValue.getClass()); + encodeChildElementToStreamWriter(theWriter, nextValue, childName, childDef); + } + + // child extensions + encodeExtensionsIfPresent(theWriter, next); + + theWriter.writeEndElement(); + } + } + } + + private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException { + if (theDt == null || theDt.getValue() == null) { + return; + } + + boolean firstEvent = true; + for (XMLEvent event : theDt.getValue()) { + switch (event.getEventType()) { + case XMLStreamConstants.ATTRIBUTE: + Attribute attr = (Attribute) event; + if (isBlank(attr.getName().getPrefix())) { + if (isBlank(attr.getName().getNamespaceURI())) { + theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue()); + } else { + theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue()); + } + } else { + theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue()); + } + + break; + case XMLStreamConstants.CDATA: + theEventWriter.writeCData(((Characters) event).getData()); + break; + case XMLStreamConstants.CHARACTERS: + case XMLStreamConstants.SPACE: + theEventWriter.writeCharacters(((Characters) event).getData()); + break; + case XMLStreamConstants.COMMENT: + theEventWriter.writeComment(((Comment) event).getText()); + break; + case XMLStreamConstants.END_ELEMENT: + theEventWriter.writeEndElement(); + break; + case XMLStreamConstants.ENTITY_REFERENCE: + EntityReference er = (EntityReference) event; + theEventWriter.writeEntityRef(er.getName()); + break; + case XMLStreamConstants.NAMESPACE: + Namespace ns = (Namespace) event; + theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI()); + break; + case XMLStreamConstants.START_ELEMENT: + StartElement se = event.asStartElement(); + if (firstEvent) { + theEventWriter.writeStartElement(se.getName().getLocalPart()); + theEventWriter.writeNamespace(se.getName().getPrefix(), se.getName().getNamespaceURI()); + } else { + if (isBlank(se.getName().getPrefix())) { + if (isBlank(se.getName().getNamespaceURI())) { + theEventWriter.writeStartElement(se.getName().getLocalPart()); + } else { + theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart()); + } + } else { + theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI()); + } + } + break; + case XMLStreamConstants.DTD: + case XMLStreamConstants.END_DOCUMENT: + case XMLStreamConstants.ENTITY_DECLARATION: + case XMLStreamConstants.NOTATION_DECLARATION: + case XMLStreamConstants.PROCESSING_INSTRUCTION: + case XMLStreamConstants.START_DOCUMENT: + break; + } + + firstEvent = false; + } + } + private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReference theRef) throws XMLStreamException { if (StringUtils.isNotBlank(theRef.getDisplay())) { theEventWriter.writeStartElement("display"); @@ -138,21 +264,29 @@ public class XmlParser { XMLEvent nextEvent = streamReader.nextEvent(); if (nextEvent.isStartElement()) { StartElement elem = nextEvent.asStartElement(); - if (!FHIR_NS.equals(elem.getName().getNamespaceURI())) { + + String namespaceURI = elem.getName().getNamespaceURI(); + if (!FHIR_NS.equals(namespaceURI) && !XHTML_NS.equals(namespaceURI)) { continue; } if (parserState == null) { parserState = ParserState.getResourceInstance(myContext, elem.getName().getLocalPart()); + } else if ("extension".equals(elem.getName().getLocalPart())) { + Attribute urlAttr = elem.getAttributeByName(new QName("url")); + if (urlAttr==null||isBlank(urlAttr.getValue())) { + throw new DataFormatException("Extension element has no 'url' attribute"); + } + parserState.enteringNewElementExtension(elem, urlAttr.getValue()); } else { - parserState.enteringNewElement(elem.getName().getLocalPart()); + parserState.enteringNewElement(elem, elem.getName().getLocalPart()); } for (@SuppressWarnings("unchecked") Iterator<Attribute> iter = elem.getAttributes(); iter.hasNext();) { Attribute next = iter.next(); if (next.getName().getLocalPart().equals("value")) { - parserState.attributeValue(next.getValue()); + parserState.attributeValue(next, next.getValue()); } } @@ -167,12 +301,14 @@ public class XmlParser { if (parserState == null) { throw new DataFormatException("Detected attribute before element"); } - parserState.attributeValue(elem.getValue()); + parserState.attributeValue(elem, elem.getValue()); } else if (nextEvent.isEndElement()) { EndElement elem = nextEvent.asEndElement(); - if (!FHIR_NS.equals(elem.getName().getNamespaceURI())) { + String namespaceURI = elem.getName().getNamespaceURI(); + if (!FHIR_NS.equals(namespaceURI) && !XHTML_NS.equals(namespaceURI)) { continue; } + if (parserState == null) { throw new DataFormatException("Detected unexpected end-element"); } @@ -181,7 +317,9 @@ public class XmlParser { return (IResource) parserState.getObject(); } } else { - parserState.otherEvent(nextEvent); + if (parserState != null) { + parserState.otherEvent(nextEvent); + } } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java index 549060c0a7c..5ee550fb4dc 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.datatype.DateDt; import ca.uhn.fhir.model.resource.Observation; public class XmlParserTest { diff --git a/hapi-fhir-base/src/test/resources/observation-example-eeg.xml b/hapi-fhir-base/src/test/resources/observation-example-eeg.xml index 32bb3f283cc..00f09b24d3f 100644 --- a/hapi-fhir-base/src/test/resources/observation-example-eeg.xml +++ b/hapi-fhir-base/src/test/resources/observation-example-eeg.xml @@ -1,39 +1,51 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- - This example is taken from the v3 data types (SLIST) ---> - +<!-- This example is taken from the v3 data types (SLIST) --> + <Observation xmlns="http://hl7.org/fhir" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://hl7.org/fhir ..\..\schema\observation.xsd"> - <text> - <status value="generated"/> - <div xmlns="http://www.w3.org/1999/xhtml">Sept 17, 2012: Systolic Blood pressure 107 mmHg (normal)</div> - </text> - <name> - <!-- Actually, this is not a vull EEG. A todo is to turn it into one --> - <coding> - <system value="http://loinc.org"/> - <code value="11523-8"/> - <display value="EEG study"/> - </coding> - </name> - <valueSampledData> - <origin> - <value value="0"/> - <units value="μV"/> - <system value="http://unitsofmeasure.org"/> - <code value="uV"/> - </origin> - <period value="100"/> - <factor value="2.5"/> - <dimensions value="1"/> - <data value="-4 -13 -18 -18 -18 -17 -16 -16 -16 -16 -16 -17 -18 -18 -1 -17 -16 -16 -16 -15 -13 -11 -10 -10 -9 -6 -4 -5 -5 -3 -2 -2 -1 1 2 7 8 9 10 11 12 13 15 17 19 21 23 25 27 29 30 30 31 34 37 40 43 45 4 46 46 46 46 47 49 51 53 55 57 59 60 59 58 58 58 57 56 56 56 57 57 5 53 50 47 45 74 51 38 33 31 2 25 21 16 14 15 13 9 7 4 1 -1 -3 -4 -6 -10 -12 -13 -12 -12 -17 -18 -18 -18 -19 -20 -21 -20 -20 -20 -20 -2 2 1 0 0 0 1 2 2 1 1 1 0 -1 0 1 1 1 1 2 E"/> - </valueSampledData> - - <status value="final"/> - <reliability value="ok"/> + <extension url="http://acme.org/fhir/Profile/main#trial-status"> + <extension url="http://acme.org/fhir/Profile/main#trial-status-code"> + <valueCode value="unsure" /> + </extension> + <extension url="http://acme.org/fhir/Profile/main#trial-status-date"> + <valueDate value="2009-03-14" /> + </extension> + <extension url="http://acme.org/fhir/Profile/main#trial-status-who"> + <valueResource> + <reference value="Practitioner/example" /> + </valueResource> + </extension> + </extension> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml">Sept 17, 2012: Systolic Blood pressure 107 mmHg (normal)</div> + </text> + <name> + <!-- Actually, this is not a vull EEG. A todo is to turn it into one --> + <coding> + <system value="http://loinc.org" /> + <code value="11523-8" /> + <display value="EEG study" /> + </coding> + </name> + <valueSampledData> + <origin> + <value value="0" /> + <units value="μV" /> + <system value="http://unitsofmeasure.org" /> + <code value="uV" /> + </origin> + <period value="100" /> + <factor value="2.5" /> + <dimensions value="1" /> + <data + value="-4 -13 -18 -18 -18 -17 -16 -16 -16 -16 -16 -17 -18 -18 -1 -17 -16 -16 -16 -15 -13 -11 -10 -10 -9 -6 -4 -5 -5 -3 -2 -2 -1 1 2 7 8 9 10 11 12 13 15 17 19 21 23 25 27 29 30 30 31 34 37 40 43 45 4 46 46 46 46 47 49 51 53 55 57 59 60 59 58 58 58 57 56 56 56 57 57 5 53 50 47 45 74 51 38 33 31 2 25 21 16 14 15 13 9 7 4 1 -1 -3 -4 -6 -10 -12 -13 -12 -12 -17 -18 -18 -18 -19 -20 -21 -20 -20 -20 -20 -2 2 1 0 0 0 1 2 2 1 1 1 0 -1 0 1 1 1 1 2 E" /> + </valueSampledData> + + <status value="final" /> + <reliability value="ok" /> - <subject> - <reference value="Patient/example"/> - </subject> + <subject> + <reference value="Patient/example" /> + </subject> </Observation> \ No newline at end of file diff --git a/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/BaseParser.java b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/BaseParser.java index 4191f893913..bfbfdfe9368 100644 --- a/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/BaseParser.java +++ b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/BaseParser.java @@ -8,9 +8,11 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; @@ -19,30 +21,29 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.qos.logback.core.db.dialect.MySQLDialect; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.ResourceReference; -import ca.uhn.fhir.model.datatype.CodeDt; -import ca.uhn.fhir.model.datatype.CodeableConceptDt; import ca.uhn.fhir.starter.model.BaseElement; import ca.uhn.fhir.starter.model.Child; +import ca.uhn.fhir.starter.model.Extension; import ca.uhn.fhir.starter.model.Resource; import ca.uhn.fhir.starter.model.ResourceBlock; import ca.uhn.fhir.starter.util.XMLUtils; public abstract class BaseParser { - private String myDirectory; - private String myOutputFile; - private int myColName; - private int myColCard; - private int myColType; - private int myColBinding; - private int myColShortName; - private int myColDefinition; - private int myColV2Mapping; - private int myColRequirements; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); + private int myColBinding; + private int myColCard; + private int myColDefinition; + private int myColName; + private int myColRequirements; + private int myColShortName; + private int myColType; + private int myColV2Mapping; + private String myDirectory; + private ArrayList<Extension> myExtensions; + private String myOutputFile; public void parse() throws Exception { File baseDir = new File(myDirectory); @@ -145,66 +146,14 @@ public abstract class BaseParser { myDirectory = theDirectory; } + public void setExtensions(ArrayList<Extension> theExts) { + myExtensions = theExts; + } + public void setOutputFile(String theOutputFile) { myOutputFile = theOutputFile; } - static String cellValue(Node theRowXml, int theCellIndex) { - NodeList cells = ((Element) theRowXml).getElementsByTagName("Cell"); - - for (int i = 0, currentCell = 0; i < cells.getLength(); i++) { - Element nextCell = (Element) cells.item(i); - String indexVal = nextCell.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Index"); - if (StringUtils.isNotBlank(indexVal)) { - // 1-indexed for some reason... - currentCell = Integer.parseInt(indexVal) - 1; - } - - if (currentCell == theCellIndex) { - NodeList dataElems = nextCell.getElementsByTagName("Data"); - Element dataElem = (Element) dataElems.item(0); - if (dataElem == null) { - return null; - } - String retVal = dataElem.getTextContent(); - return retVal; - } - - currentCell++; - } - - return null; - } - - private void write(Resource theResource) throws IOException { - File f = new File(myOutputFile); - FileWriter w = new FileWriter(f, false); - - ourLog.info("Writing file: {}", f.getAbsolutePath()); - - VelocityContext ctx = new VelocityContext(); - ctx.put("className", theResource.getName()); - ctx.put("shortName", defaultString(theResource.getShortName())); - ctx.put("definition", defaultString(theResource.getDefinition())); - ctx.put("requirements", defaultString(theResource.getRequirement())); - ctx.put("children", theResource.getChildren()); - ctx.put("resourceBlockChildren", theResource.getResourceBlockChildren()); - - VelocityEngine v = new VelocityEngine(); - v.setProperty("resource.loader", "cp"); - v.setProperty("cp.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - - InputStream templateIs = ResourceParser.class.getResourceAsStream(getTemplate()); - InputStreamReader templateReader = new InputStreamReader(templateIs); - v.evaluate(ctx, w, "", templateReader); - - w.close(); - } - - protected abstract String getFilename(); - - protected abstract String getTemplate(); - private void parseFirstRow(Element theDefRow) { for (int i = 0; i < 20; i++) { String nextName = cellValue(theDefRow, i); @@ -232,6 +181,36 @@ public abstract class BaseParser { } } + private void write(Resource theResource) throws IOException { + File f = new File(myOutputFile); + FileWriter w = new FileWriter(f, false); + + ourLog.info("Writing file: {}", f.getAbsolutePath()); + + VelocityContext ctx = new VelocityContext(); + ctx.put("className", theResource.getName()); + ctx.put("shortName", defaultString(theResource.getShortName())); + ctx.put("definition", defaultString(theResource.getDefinition())); + ctx.put("requirements", defaultString(theResource.getRequirement())); + ctx.put("children", theResource.getChildren()); + ctx.put("resourceBlockChildren", theResource.getResourceBlockChildren()); + ctx.put("childExtensionTypes", ObjectUtils.defaultIfNull(myExtensions, new ArrayList<Extension>())); + + VelocityEngine v = new VelocityEngine(); + v.setProperty("resource.loader", "cp"); + v.setProperty("cp.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + + InputStream templateIs = ResourceParser.class.getResourceAsStream(getTemplate()); + InputStreamReader templateReader = new InputStreamReader(templateIs); + v.evaluate(ctx, w, "", templateReader); + + w.close(); + } + + protected abstract String getFilename(); + + protected abstract String getTemplate(); + protected void parseBasicElements(Element theRowXml, BaseElement theTarget) { String name = cellValue(theRowXml, myColName); theTarget.setName(name); @@ -263,4 +242,31 @@ public abstract class BaseParser { theTarget.setV2Mapping(cellValue(theRowXml, myColV2Mapping)); } + static String cellValue(Node theRowXml, int theCellIndex) { + NodeList cells = ((Element) theRowXml).getElementsByTagName("Cell"); + + for (int i = 0, currentCell = 0; i < cells.getLength(); i++) { + Element nextCell = (Element) cells.item(i); + String indexVal = nextCell.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Index"); + if (StringUtils.isNotBlank(indexVal)) { + // 1-indexed for some reason... + currentCell = Integer.parseInt(indexVal) - 1; + } + + if (currentCell == theCellIndex) { + NodeList dataElems = nextCell.getElementsByTagName("Data"); + Element dataElem = (Element) dataElems.item(0); + if (dataElem == null) { + return null; + } + String retVal = dataElem.getTextContent(); + return retVal; + } + + currentCell++; + } + + return null; + } + } diff --git a/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/ResourceParser.java b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/ResourceParser.java index 93f82ff8468..d519d2a4d87 100644 --- a/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/ResourceParser.java +++ b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/ResourceParser.java @@ -1,5 +1,10 @@ package ca.uhn.fhir.starter; +import java.util.ArrayList; + +import ca.uhn.fhir.model.datatype.DateDt; +import ca.uhn.fhir.model.datatype.StringDt; +import ca.uhn.fhir.starter.model.Extension; public class ResourceParser extends BaseParser { private String myResourceName; @@ -13,50 +18,59 @@ public class ResourceParser extends BaseParser { return myResourceName + "-spreadsheet.xml"; } + // @Override + // protected void parseBasicElements(Element theRowXml, BaseElement + // theTarget) { + // String name = cellValue(theRowXml, 0); + // theTarget.setName(name); + // + // int lastDot = name.lastIndexOf('.'); + // if (lastDot == -1) { + // theTarget.setElementName(name); + // } else { + // String elementName = name.substring(lastDot + 1); + // String elementParentName = name.substring(0, lastDot); + // theTarget.setElementName(elementName); + // theTarget.setElementParentName(elementParentName); + // } + // + // String cardValue = cellValue(theRowXml, 1); + // if (cardValue != null && cardValue.contains("..")) { + // String[] split = cardValue.split("\\.\\."); + // theTarget.setCardMin(split[0]); + // theTarget.setCardMax(split[1]); + // } + // + // String type = cellValue(theRowXml, 5); + // theTarget.setTypeFromString(type); + // + // theTarget.setBinding(cellValue(theRowXml, 6)); + // theTarget.setShortName(cellValue(theRowXml, 7)); + // theTarget.setDefinition(cellValue(theRowXml, 8)); + // theTarget.setRequirement(cellValue(theRowXml, 9)); + // theTarget.setV2Mapping(cellValue(theRowXml, 14)); + // } + @Override protected String getTemplate() { return "/resource.vm"; } -// @Override -// protected void parseBasicElements(Element theRowXml, BaseElement theTarget) { -// String name = cellValue(theRowXml, 0); -// theTarget.setName(name); -// -// int lastDot = name.lastIndexOf('.'); -// if (lastDot == -1) { -// theTarget.setElementName(name); -// } else { -// String elementName = name.substring(lastDot + 1); -// String elementParentName = name.substring(0, lastDot); -// theTarget.setElementName(elementName); -// theTarget.setElementParentName(elementParentName); -// } -// -// String cardValue = cellValue(theRowXml, 1); -// if (cardValue != null && cardValue.contains("..")) { -// String[] split = cardValue.split("\\.\\."); -// theTarget.setCardMin(split[0]); -// theTarget.setCardMax(split[1]); -// } -// -// String type = cellValue(theRowXml, 5); -// theTarget.setTypeFromString(type); -// -// theTarget.setBinding(cellValue(theRowXml, 6)); -// theTarget.setShortName(cellValue(theRowXml, 7)); -// theTarget.setDefinition(cellValue(theRowXml, 8)); -// theTarget.setRequirement(cellValue(theRowXml, 9)); -// theTarget.setV2Mapping(cellValue(theRowXml, 14)); -// } - public static void main(String[] args) throws Exception { ResourceParser p = new ResourceParser(); p.setDirectory("src/test/resources/res"); p.setResourceName("patient"); p.setOutputFile("../hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Patient.java"); + ArrayList<Extension> exts = new ArrayList<Extension>(); + Extension ext1 = new Extension("foo1", "http://foo/1", StringDt.class); + exts.add(ext1); + Extension ext2 = new Extension("bar1", "http://bar/1", new Extension("bar11", "http://bar/1/1", DateDt.class), new Extension("bar12", "http://bar/1/2", DateDt.class)); + exts.add(ext2); + p.setExtensions(exts); p.parse(); + p = new ResourceParser(); + p.setDirectory("src/test/resources/res"); p.setResourceName("observation"); p.setOutputFile("../hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java"); p.parse(); diff --git a/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/model/Extension.java b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/model/Extension.java new file mode 100644 index 00000000000..8cdc6e3e534 --- /dev/null +++ b/hapi-fhir-starter/src/main/java/ca/uhn/fhir/starter/model/Extension.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.starter.model; + +import java.util.Arrays; +import java.util.List; + +import ca.uhn.fhir.model.api.IDatatype; + +public class Extension { + + private List<Extension> myChildExtensions; + private Class<? extends IDatatype> myDatatype; + private String myName; + private String myUrl; + + public Extension() { + super(); + } + + public boolean isHasDatatype() { + return myDatatype != null; + } + + public String getDatatypeSimpleName() { + return myDatatype.getSimpleName(); + } + + public String getNameType() { + return getName().substring(0, 1).toUpperCase()+getName().substring(1); + } + + public Extension(String theName, String theUrl, Class<? extends IDatatype> theDatatype) { + setName(theName); + setUrl(theUrl); + setDatatype(theDatatype); + } + + public Extension(String theName, String theUrl, Extension... theChildExtensions) { + setName(theName); + setUrl(theUrl); + if (theChildExtensions != null) { + setChildExtensions(Arrays.asList(theChildExtensions)); + } + } + + public List<Extension> getChildExtensions() { + return myChildExtensions; + } + + public Class<? extends IDatatype> getDatatype() { + return myDatatype; + } + + public String getName() { + return myName; + } + + public String getUrl() { + return myUrl; + } + + public void setChildExtensions(List<Extension> theChildExtensions) { + if (theChildExtensions != null && theChildExtensions.size() > 0 && myDatatype != null) { + throw new IllegalArgumentException("Extension may not have a datatype AND child extensions"); + } + myChildExtensions = theChildExtensions; + } + + public void setDatatype(Class<? extends IDatatype> theDatatype) { + if (myChildExtensions != null && myChildExtensions.size() > 0 && theDatatype != null) { + throw new IllegalArgumentException("Extension may not have a datatype AND child extensions"); + } + myDatatype = theDatatype; + } + + public void setName(String theName) { + // TODO: validate that this is a valid name (no punctuation, spaces, + // etc.) + myName = theName; + } + + public void setUrl(String theUrl) { + myUrl = theUrl; + } +} diff --git a/hapi-fhir-starter/src/main/resources/dt_composite.vm b/hapi-fhir-starter/src/main/resources/dt_composite.vm index bd8933336d5..c1c61829a05 100644 --- a/hapi-fhir-starter/src/main/resources/dt_composite.vm +++ b/hapi-fhir-starter/src/main/resources/dt_composite.vm @@ -24,7 +24,10 @@ import ca.uhn.fhir.model.datatype.*; @DatatypeDef(name="${className}") public class ${className}Dt extends BaseCompositeDatatype { +#childExtensionFields( $childExtensionTypes ) #childVars( $children ) #childAccessors( $children ) +#childExtensionTypes( $childExtensionTypes ) + } \ No newline at end of file diff --git a/hapi-fhir-starter/src/main/resources/resource.vm b/hapi-fhir-starter/src/main/resources/resource.vm index c467642f44a..c5e1b86280b 100644 --- a/hapi-fhir-starter/src/main/resources/resource.vm +++ b/hapi-fhir-starter/src/main/resources/resource.vm @@ -24,6 +24,7 @@ import ca.uhn.fhir.model.datatype.*; @ResourceDef(name="${className}") public class ${className} extends BaseResource { +#childExtensionFields( $childExtensionTypes ) #childVars( $children ) #childAccessors( $children ) @@ -44,4 +45,6 @@ public class ${className} extends BaseResource { #end +#childExtensionTypes( $childExtensionTypes ) + } \ No newline at end of file diff --git a/hapi-fhir-starter/src/main/resources/templates.vm b/hapi-fhir-starter/src/main/resources/templates.vm index c625d7b2f7a..ebe309b024a 100644 --- a/hapi-fhir-starter/src/main/resources/templates.vm +++ b/hapi-fhir-starter/src/main/resources/templates.vm @@ -1,3 +1,7 @@ +################################################################## +## childVars +################################################################## + #macro ( childVars $childElements ) #foreach ( $child in $children ) #if ($child.resourceRef) @@ -21,6 +25,11 @@ #end #end + +################################################################## +## childAccessors +################################################################## + #macro ( childAccessors $childElements ) #foreach ( $child in $children ) /** @@ -49,3 +58,53 @@ #end #end + +################################################################## +## childExtensionFields +################################################################## + +#macro ( childExtensionFields $childExtensionTypes ) +#foreach ( $extensionType in $childExtensionTypes ) + @Child() + private ${extensionType.nameType} my${extensionType.nameType}; + +#end +#end + + +################################################################## +## childExtensionTypes +################################################################## + +#macro ( childExtensionTypes $childExtensionTypes ) +#foreach ( $extensionType in $childExtensionTypes ) + @ExtensionBlock(url="${extensionType.url}") + public class ${extensionType.nameType} implements IExtension { + +#if(${extensionType.hasDatatype}) + @Child(name="value", order=0) + private ${extensionType.datatypeSimpleName} myValue; + + /** + * Gets the value + */ + public ${extensionType.datatypeSimpleName} getValue() { + return myValue; + } + + /** + * Sets the value + */ + public void setValue(${extensionType.datatypeSimpleName} theValue) { + myValue = theValue; + } +#else +#end + + + } + +#end +#end + +