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 bfc74a10193..79b204fc342 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 @@ -51,16 +51,20 @@ import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.validation.FhirValidator; /** - * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then used as a factory for various other types of objects (parsers, clients, etc.). + * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then + * used as a factory for various other types of objects (parsers, clients, etc.). * *

* Important usage notes: *

*

*/ @@ -75,7 +79,7 @@ public class FhirContext { private volatile INarrativeGenerator myNarrativeGenerator; private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; - private IFhirVersion myVersion; + private final IFhirVersion myVersion; /** * Default constructor. In most cases this is the right constructor to use. @@ -93,18 +97,31 @@ public class FhirContext { } public FhirContext(Collection> theResourceTypes) { - scanResourceTypes(theResourceTypes); - - if (FhirVersionEnum.DSTU1.isPresentOnClasspath()) { + this(null, theResourceTypes); + } + + public FhirContext(FhirVersionEnum theVersion) { + this(theVersion, null); + } + + private FhirContext(FhirVersionEnum theVersion, Collection> theResourceTypes) { + if (theVersion != null) { + if (!theVersion.isPresentOnClasspath()) { + throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion")); + } + myVersion = theVersion.getVersionImplementation(); + } else if (FhirVersionEnum.DSTU1.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU1.getVersionImplementation(); } else { - throw new IllegalStateException("Could not find any HAPI-FHIR structure JARs on the classpath. Note that as of HAPI-FHIR v0.8, a separate FHIR strcture JAR must be added to your classpath or project pom.xml"); + throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructures")); } + scanResourceTypes(theResourceTypes); } /** - * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. */ public BaseRuntimeElementDefinition getElementDefinition(Class theElementType) { return myClassToElementDefinition.get(theElementType); @@ -116,7 +133,8 @@ public class FhirContext { } /** - * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with caution + * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with + * caution */ public HapiLocalizer getLocalizer() { if (myLocalizer == null) { @@ -130,7 +148,8 @@ public class FhirContext { } /** - * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinition(Class theResourceType) { RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType); @@ -141,21 +160,24 @@ public class FhirContext { } /** - * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) { return getResourceDefinition(theResource.getClass()); } /** - * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. */ @SuppressWarnings("unchecked") public RuntimeResourceDefinition getResourceDefinition(String theResourceName) { String resourceName = theResourceName; /* - * TODO: this is a bit of a hack, really we should have a translation table based on a property file or something so that we can detect names like diagnosticreport + * TODO: this is a bit of a hack, really we should have a translation table based on a property file or + * something so that we can detect names like diagnosticreport */ if (Character.isLowerCase(resourceName.charAt(0))) { resourceName = WordUtils.capitalize(resourceName); @@ -189,14 +211,16 @@ public class FhirContext { } /** - * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinitionById(String theId) { return myIdToResourceDefinition.get(theId); } /** - * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the core library. + * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the + * core library. */ public Collection getResourceDefinitions() { return myIdToResourceDefinition.values(); @@ -221,10 +245,12 @@ public class FhirContext { * Create and return a new JSON parser. * *

- * Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread or every message being parsed/encoded. + * Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread + * or every message being parsed/encoded. *

*

- * Performance Note: This method is cheap to call, and may be called once for every message being processed without incurring any performance penalty + * Performance Note: This method is cheap to call, and may be called once for every message being processed + * without incurring any performance penalty *

*/ public IParser newJsonParser() { @@ -232,12 +258,16 @@ public class FhirContext { } /** - * Instantiates a new client instance. This method requires an interface which is defined specifically for your use cases to contain methods for each of the RESTful operations you wish to - * implement (e.g. "read ImagingStudy", "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its sub-interface {@link IBasicClient}). See the RESTful Client documentation for more information on how to define this interface. + * Instantiates a new client instance. This method requires an interface which is defined specifically for your use + * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy", + * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its + * sub-interface {@link IBasicClient}). See the RESTful Client documentation for more + * information on how to define this interface. * *

- * Performance Note: This method is cheap to call, and may be called once for every operation invocation without incurring any performance penalty + * Performance Note: This method is cheap to call, and may be called once for every operation invocation + * without incurring any performance penalty *

* * @param theClientType @@ -253,11 +283,13 @@ public class FhirContext { } /** - * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against a compliant server, but does not have methods defining the specific - * functionality required (as is the case with {@link #newRestfulClient(Class, String) non-generic clients}). + * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against + * a compliant server, but does not have methods defining the specific functionality required (as is the case with + * {@link #newRestfulClient(Class, String) non-generic clients}). * *

- * Performance Note: This method is cheap to call, and may be called once for every operation invocation without incurring any performance penalty + * Performance Note: This method is cheap to call, and may be called once for every operation invocation + * without incurring any performance penalty *

* * @param theServerBase @@ -284,10 +316,12 @@ public class FhirContext { * Create and return a new XML parser. * *

- * Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread or every message being parsed/encoded. + * Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread + * or every message being parsed/encoded. *

*

- * Performance Note: This method is cheap to call, and may be called once for every message being processed without incurring any performance penalty + * Performance Note: This method is cheap to call, and may be called once for every message being processed + * without incurring any performance penalty *

*/ public IParser newXmlParser() { @@ -329,7 +363,8 @@ public class FhirContext { } /** - * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with caution + * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with + * caution */ public void setLocalizer(HapiLocalizer theMessages) { myLocalizer = theMessages; @@ -357,4 +392,18 @@ public class FhirContext { return retVal; } + /** + * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU1} + */ + public static FhirContext forDstu1() { + return new FhirContext(FhirVersionEnum.DSTU1); + } + + /** + * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DEV} + */ + public static FhirContext forDev() { + return new FhirContext(FhirVersionEnum.DEV); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java index a34d7e71c53..0e444940573 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java @@ -25,7 +25,15 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; public enum FhirVersionEnum { - DSTU1("ca.uhn.fhir.model.dstu.FhirDstu1"); + /* + * *********************** + * Don't sort this type!!! + * *********************** + */ + + DSTU1("ca.uhn.fhir.model.dstu.FhirDstu1"), + + DEV("ca.uhn.fhir.model.dev.FhirDev"); private final String myVersionClass; private volatile Boolean myPresentOnClasspath; @@ -35,6 +43,10 @@ public enum FhirVersionEnum { myVersionClass = theVersionClass; } + public boolean isNewerThan(FhirVersionEnum theVersion) { + return ordinal() > theVersion.ordinal(); + } + /** * Returns true if the given version is present on the classpath */ 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 544d266b147..5e8e167cf37 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 @@ -20,7 +20,7 @@ package ca.uhn.fhir.context; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.io.IOException; import java.io.InputStream; @@ -118,8 +118,14 @@ class ModelScanner { } ModelScanner(FhirContext theContext, Map, BaseRuntimeElementDefinition> theExistingDefinitions, Collection> theResourceTypes) throws ConfigurationException { - myContext=theContext; - init(theExistingDefinitions, new HashSet>(theResourceTypes)); + myContext = theContext; + Set> toScan; + if (theResourceTypes != null) { + toScan = new HashSet>(theResourceTypes); + } else { + toScan = new HashSet>(); + } + init(theExistingDefinitions, toScan); } public Map, BaseRuntimeElementDefinition> getClassToElementDefinitions() { @@ -188,13 +194,7 @@ class ModelScanner { int startSize = myClassToElementDefinitions.size(); long start = System.currentTimeMillis(); - InputStream str = ModelScanner.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/fhirversion.properties"); - if (str == null) { - str = ModelScanner.class.getResourceAsStream("ca/uhn/fhir/model/dstu/fhirversion.properties"); - } - if (str == null) { - throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties"); - } + InputStream str = myContext.getVersion().getFhirVersionPropertiesFile(); Properties prop = new Properties(); try { prop.load(str); @@ -399,13 +399,13 @@ class ModelScanner { for (Field next : theClass.getDeclaredFields()) { if (Modifier.isFinal(next.getModifiers())) { - ourLog.trace("Ignoring constant {} on target type {}", next.getName(), theClass); + ourLog.trace("Ignoring constant {} on target type {}", next.getName(), theClass); continue; } Child childAnnotation = pullAnnotation(next, Child.class); if (childAnnotation == null) { - ourLog.trace("Ignoring non @Child field {} on target type {}",next.getName() , theClass); + ourLog.trace("Ignoring non @Child field {} on target type {}", next.getName(), theClass); continue; } @@ -435,8 +435,8 @@ class ModelScanner { } } if (order == Child.REPLACE_PARENT) { - throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT + ") but no parent element with extension URL " + extensionAttr.url() - + " could be found on type " + next.getDeclaringClass().getSimpleName()); + throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT + ") but no parent element with extension URL " + extensionAttr.url() + " could be found on type " + + next.getDeclaringClass().getSimpleName()); } } else { @@ -467,7 +467,7 @@ class ModelScanner { int min = childAnnotation.min(); int max = childAnnotation.max(); /* - * Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict wityh any + * Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any * given IDs and can be figured out later */ while (order == Child.ORDER_UNKNOWN && orderMap.containsKey(order)) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java index 2a627159977..f0e7c5c0db0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/i18n/HapiLocalizer.java @@ -37,23 +37,27 @@ public class HapiLocalizer { myBundle = ResourceBundle.getBundle(HapiLocalizer.class.getPackage().getName() + ".hapi-messages"); } - public String getMessage(String theKey, Object... theParameters) { + public String getMessage(Class theType, String theKey, Object... theParameters) { + return getMessage(theType.getName() + '.' + theKey, theParameters); + } + + public String getMessage(String theQualifiedKey, Object... theParameters) { if (theParameters != null && theParameters.length > 0) { - MessageFormat format = myKeyToMessageFormat.get(theKey); + MessageFormat format = myKeyToMessageFormat.get(theQualifiedKey); if (format != null) { return format.format(theParameters).toString(); } - String formatString = myBundle.getString(theKey); + String formatString = myBundle.getString(theQualifiedKey); if (formatString== null) { formatString = "!MESSAGE!"; } format = new MessageFormat(formatString); - myKeyToMessageFormat.put(theKey, format); + myKeyToMessageFormat.put(theQualifiedKey, format); return format.format(theParameters).toString(); } else { - String retVal = myBundle.getString(theKey); + String retVal = myBundle.getString(theQualifiedKey); if (retVal == null) { retVal = "!MESSAGE!"; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java index 4485e308763..9289388e3b7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java @@ -20,20 +20,18 @@ package ca.uhn.fhir.model.api; * #L% */ -import java.util.HashMap; -import java.util.Map; - -import ca.uhn.fhir.rest.gclient.StringClientParam; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; +import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.util.ElementUtil; public abstract class BaseResource extends BaseElement implements IResource { @@ -70,7 +68,7 @@ public abstract class BaseResource extends BaseElement implements IResource { @Child(name = "language", order = 0, min = 0, max = Child.MAX_UNLIMITED) private CodeDt myLanguage; - private Map, Object> myResourceMetadata; + private ResourceMetadataMap myResourceMetadata; @Child(name = "text", order = 1, min = 0, max = 1) private NarrativeDt myText; @@ -99,9 +97,9 @@ public abstract class BaseResource extends BaseElement implements IResource { } @Override - public Map, Object> getResourceMetadata() { + public ResourceMetadataMap getResourceMetadata() { if (myResourceMetadata == null) { - myResourceMetadata = new HashMap, Object>(); + myResourceMetadata = new ResourceMetadataMap(); } return myResourceMetadata; } @@ -136,7 +134,7 @@ public abstract class BaseResource extends BaseElement implements IResource { } @Override - public void setResourceMetadata(Map, Object> theMap) { + public void setResourceMetadata(ResourceMetadataMap theMap) { Validate.notNull(theMap, "The Map must not be null"); myResourceMetadata = theMap; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java index 8e70a3666ed..fa42651ce7b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java @@ -34,17 +34,21 @@ import org.apache.commons.lang3.builder.ToStringStyle; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; +import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.UrlUtil; public class Bundle extends BaseBundle /* implements IElement */{ + private ResourceMetadataMap myResourceMetadata; + private BoundCodeDt myType; private StringDt myBundleId; - private TagList myCategories; private List myEntries; private volatile transient Map myIdToEntries; @@ -54,13 +58,12 @@ public class Bundle extends BaseBundle /* implements IElement */{ private StringDt myLinkNext; private StringDt myLinkPrevious; private StringDt myLinkSelf; - private InstantDt myPublished; private StringDt myTitle; private IntegerDt myTotalResults; - private InstantDt myUpdated; /** - * @deprecated Tags wil become immutable in a future release of HAPI, so {@link #addCategory(String, String, String)} should be used instead + * @deprecated Tags wil become immutable in a future release of HAPI, so + * {@link #addCategory(String, String, String)} should be used instead */ public Tag addCategory() { Tag retVal = new Tag(); @@ -72,7 +75,6 @@ public class Bundle extends BaseBundle /* implements IElement */{ getCategories().add(new Tag(theScheme, theTerm, theLabel)); } - public void addCategory(Tag theTag) { getCategories().add(theTag); } @@ -119,10 +121,10 @@ public class Bundle extends BaseBundle /* implements IElement */{ if (theResource.getId() != null) { if (theResource.getId().isAbsolute()) { - + entry.getLinkSelf().setValue(theResource.getId().getValue()); entry.getId().setValue(theResource.getId().toVersionless().getValue()); - + } else if (StringUtils.isNotBlank(theResource.getId().getValue())) { StringBuilder b = new StringBuilder(); @@ -208,6 +210,27 @@ public class Bundle extends BaseBundle /* implements IElement */{ return entry; } + public StringDt getBundleId() { + if (myBundleId == null) { + myBundleId = new StringDt(); + } + return myBundleId; + } + + public TagList getCategories() { + if (myCategories == null) { + myCategories = new TagList(); + } + return myCategories; + } + + public List getEntries() { + if (myEntries == null) { + myEntries = new ArrayList(); + } + return myEntries; + } + // public static void main(String[] args) { // // FhirContext ctx = new FhirContext(); @@ -234,27 +257,6 @@ public class Bundle extends BaseBundle /* implements IElement */{ // } // - public StringDt getBundleId() { - if (myBundleId == null) { - myBundleId = new StringDt(); - } - return myBundleId; - } - - public TagList getCategories() { - if (myCategories == null) { - myCategories = new TagList(); - } - return myCategories; - } - - public List getEntries() { - if (myEntries == null) { - myEntries = new ArrayList(); - } - return myEntries; - } - public StringDt getLinkBase() { if (myLinkBase == null) { myLinkBase = new StringDt(); @@ -298,18 +300,22 @@ public class Bundle extends BaseBundle /* implements IElement */{ } public InstantDt getPublished() { - if (myPublished == null) { - myPublished = new InstantDt(); + InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + if (retVal == null) { + retVal= new InstantDt(); + getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, retVal); } - return myPublished; + return retVal; } /** * Retrieves a resource from a bundle given its logical ID. *

- * Important usage notes: This method ignores base URLs (so passing in an ID of http://foo/Patient/123 will return a resource if it has the logical ID of - * http://bar/Patient/123. Also, this method is intended to be used for bundles which have already been populated. It will cache its results for fast performance, but that means that - * modifications to the bundle after this method is called may not be accurately reflected. + * Important usage notes: This method ignores base URLs (so passing in an ID of + * http://foo/Patient/123 will return a resource if it has the logical ID of + * http://bar/Patient/123. Also, this method is intended to be used for bundles which have already been + * populated. It will cache its results for fast performance, but that means that modifications to the bundle after + * this method is called may not be accurately reflected. *

* * @param theId @@ -330,6 +336,13 @@ public class Bundle extends BaseBundle /* implements IElement */{ return map.get(theId.toUnqualified()); } + public ResourceMetadataMap getResourceMetadata() { + if (myResourceMetadata == null) { + myResourceMetadata = new ResourceMetadataMap(); + } + return myResourceMetadata; + } + /** * Returns a list containing all resources of the given type from this bundle */ @@ -359,11 +372,20 @@ public class Bundle extends BaseBundle /* implements IElement */{ return myTotalResults; } - public InstantDt getUpdated() { - if (myUpdated == null) { - myUpdated = new InstantDt(); + public BoundCodeDt getType() { + if (myType == null) { + myType = new BoundCodeDt(BundleTypeEnum.VALUESET_BINDER); } - return myUpdated; + return myType; + } + + public InstantDt getUpdated() { + InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + if (retVal == null) { + retVal= new InstantDt(); + getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, retVal); + } + return retVal; } /** @@ -379,7 +401,11 @@ public class Bundle extends BaseBundle /* implements IElement */{ } public void setPublished(InstantDt thePublished) { - myPublished = thePublished; + getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, thePublished); + } + + public void setType(BoundCodeDt theType) { + myType = theType; } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java index cdc6f293776..8dd8adb918a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BundleEntry.java @@ -24,9 +24,13 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.CodeDt; +import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; +import ca.uhn.fhir.model.valueset.BundleEntryStatusEnum; import ca.uhn.fhir.util.ElementUtil; public class BundleEntry extends BaseBundle { @@ -38,20 +42,61 @@ public class BundleEntry extends BaseBundle { //@formatter:on private TagList myCategories; private InstantDt myDeletedAt; + private CodeDt myDeletedResourceType; + private StringDt myDeletedResourceId; + private StringDt myDeletedResourceVersion; + + public CodeDt getDeletedResourceType() { + if (myDeletedResourceType == null) { + myDeletedResourceType = new CodeDt(); + } + return myDeletedResourceType; + } + + public void setDeletedResourceType(CodeDt theDeletedResourceType) { + myDeletedResourceType = theDeletedResourceType; + } + + public StringDt getDeletedResourceId() { + if (myDeletedResourceId == null) { + myDeletedResourceId = new StringDt(); + } + return myDeletedResourceId; + } + + public void setDeletedResourceId(StringDt theDeletedResourceId) { + myDeletedResourceId = theDeletedResourceId; + } + + public StringDt getDeletedResourceVersion() { + if (myDeletedResourceVersion == null) { + myDeletedResourceVersion = new StringDt(); + } + return myDeletedResourceVersion; + } + + public void setDeletedResourceVersion(StringDt theDeletedResourceVersion) { + myDeletedResourceVersion = theDeletedResourceVersion; + } + private StringDt myDeletedByEmail; private StringDt myDeletedByName; private StringDt myDeletedComment; private StringDt myLinkAlternate; private StringDt myLinkSearch; private StringDt myLinkSelf; + private StringDt myLinkBase; private InstantDt myPublished; private IResource myResource; private XhtmlDt mySummary; private StringDt myTitle; private InstantDt myUpdated; + private BoundCodeDt myStatus; + private DecimalDt myScore; /** - * @deprecated Tags wil become immutable in a future release of HAPI, so {@link #addCategory(String, String, String)} should be used instead + * @deprecated Tags wil become immutable in a future release of HAPI, so + * {@link #addCategory(String, String, String)} should be used instead */ public Tag addCategory() { Tag retVal = new Tag(); @@ -112,6 +157,13 @@ public class BundleEntry extends BaseBundle { return myLinkAlternate; } + public StringDt getLinkBase() { + if (myLinkBase == null) { + myLinkBase = new StringDt(); + } + return myLinkBase; + } + public StringDt getLinkSearch() { if (myLinkSearch == null) { myLinkSearch = new StringDt(); @@ -137,6 +189,13 @@ public class BundleEntry extends BaseBundle { return myResource; } + public BoundCodeDt getStatus() { + if (myStatus == null) { + myStatus = new BoundCodeDt(BundleEntryStatusEnum.VALUESET_BINDER); + } + return myStatus; + } + public XhtmlDt getSummary() { if (mySummary == null) { mySummary = new XhtmlDt(); @@ -162,7 +221,7 @@ public class BundleEntry extends BaseBundle { public boolean isEmpty() { //@formatter:off return super.isEmpty() && - ElementUtil.isEmpty(myCategories, myDeletedAt, myLinkAlternate, myLinkSelf, myPublished, myResource, mySummary, myTitle, myUpdated, myDeletedByEmail, myDeletedByName, myDeletedComment); + ElementUtil.isEmpty(myDeletedResourceId, myDeletedResourceType, myDeletedResourceVersion, myScore,myStatus, myCategories, myDeletedAt, myLinkAlternate, myLinkSelf, myPublished, myResource, mySummary, myTitle, myUpdated, myDeletedByEmail, myDeletedByName, myDeletedComment); //@formatter:on } @@ -192,6 +251,10 @@ public class BundleEntry extends BaseBundle { myLinkAlternate = theLinkAlternate; } + public void setLinkBase(StringDt theLinkBase) { + myLinkBase = theLinkBase; + } + public void setLinkSearch(StringDt theLinkSearch) { myLinkSearch = theLinkSearch; } @@ -212,6 +275,10 @@ public class BundleEntry extends BaseBundle { myResource = theResource; } + public void setStatus(BoundCodeDt theStatus) { + myStatus = theStatus; + } + public void setUpdated(InstantDt theUpdated) { Validate.notNull(theUpdated, "Updated may not be null"); myUpdated = theUpdated; @@ -229,4 +296,15 @@ public class BundleEntry extends BaseBundle { return b.toString(); } + public DecimalDt getScore() { + if (myScore == null) { + myScore = new DecimalDt(); + } + return myScore; + } + + public void setScore(DecimalDt theScore) { + myScore = theScore; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java index a1b97f896a3..0b03908907d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java @@ -20,16 +20,23 @@ package ca.uhn.fhir.model.api; * #L% */ +import java.io.InputStream; + +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; public interface IFhirVersion { + FhirVersionEnum getVersion(); + IResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition); Object createServerConformanceProvider(RestfulServer theServer); IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer); + InputStream getFhirVersionPropertiesFile(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java index e6d84cd3e24..a4fcf296533 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IResource.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.model.api; import java.util.Map; import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.primitive.CodeDt; @@ -88,7 +89,7 @@ public interface IResource extends ICompositeElement, org.hl7.fhir.instance.mode * @see ResourceMetadataKeyEnum for a list of allowable keys and the object * types that values of a given key must use. */ - Map, Object> getResourceMetadata(); + ResourceMetadataMap getResourceMetadata(); /** * Returns the narrative block for this resource @@ -122,7 +123,7 @@ public interface IResource extends ICompositeElement, org.hl7.fhir.instance.mode * @throws NullPointerException * The map must not be null */ - void setResourceMetadata(Map, Object> theMap); + void setResourceMetadata(ResourceMetadataMap theMap); /** * Returns a String representing the name of this Resource. This return diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java index 104b6361daa..1c22c5798c8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -206,6 +206,24 @@ public abstract class ResourceMetadataKeyEnum { } }; + /** + * The value for this key is the version ID of the resource object. + *

+ * Values for this key are of type {@link String} + *

+ */ + public static final ResourceMetadataKeyEnum VERSION = new ResourceMetadataKeyEnum("VERSION") { + @Override + public String get(IResource theResource) { + return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION); + } + + @Override + public void put(IResource theResource, String theObject) { + theResource.getResourceMetadata().put(VERSION, theObject); + } + }; + /** * If present and populated with a string, provides the "search link" (the link element in the bundle entry with rel="search"). Server implementations may populate this with a * complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient?name=tester") in which case the server will convert diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/resource/ResourceMetadataMap.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/resource/ResourceMetadataMap.java new file mode 100644 index 00000000000..e2c20397144 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/resource/ResourceMetadataMap.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.model.base.resource; + +import java.util.HashMap; + +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; + +public class ResourceMetadataMap extends HashMap, Object> { + + private static final long serialVersionUID = 1L; + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java index 4f0a2b479d4..5bef948eac5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java @@ -44,6 +44,7 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; @@ -86,6 +87,7 @@ public class ResourceReferenceDt * @param theResource * The resource instance */ + @SimpleSetter() public ResourceReferenceDt(IResource theResource) { super(theResource); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java index fcae180a8d3..cd3c955b640 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java @@ -136,6 +136,23 @@ public class IdDt implements IPrimitiveDatatype { * The version ID ("e.g. "456") */ public IdDt(String theResourceType, String theId, String theVersionId) { + this(null,theResourceType,theId,theVersionId); + } + + /** + * Constructor + * + * @param theBaseUrl + * The server base URL (e.g. "http://example.com/fhir") + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theId + * The ID (e.g. "123") + * @param theVersionId + * The version ID ("e.g. "456") + */ + public IdDt(String theBaseUrl, String theResourceType, String theId, String theVersionId) { + myBaseUrl = theBaseUrl; myResourceType = theResourceType; myUnqualifiedId = theId; myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null); @@ -244,10 +261,21 @@ public class IdDt implements IPrimitiveDatatype { public String getValue() { if (myValue == null && myHaveComponentParts) { StringBuilder b = new StringBuilder(); + if (isNotBlank(myBaseUrl)) { + b.append(myBaseUrl); + if (myBaseUrl.charAt(myBaseUrl.length()-1)!='/') { + b.append('/'); + } + } + if (isNotBlank(myResourceType)) { b.append(myResourceType); + } + + if (b.length() > 0) { b.append('/'); } + b.append(myUnqualifiedId); if (isNotBlank(myUnqualifiedVersionId)) { b.append('/'); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig new file mode 100644 index 00000000000..9084e3999fd --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig @@ -0,0 +1,556 @@ +package ca.uhn.fhir.model.primitive; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static org.apache.commons.lang3.StringUtils.*; + +import java.math.BigDecimal; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.HashCodeBuilder; +<<<<<<< HEAD +import org.hl7.fhir.instance.model.IBaseResource; +import org.hl7.fhir.instance.model.Resource; +======= +import org.hamcrest.core.IsNot; +>>>>>>> c294e1c064fcbf112edcbf4e10c341691c12a1a8 + +import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.model.api.annotation.SimpleSetter; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.util.UrlUtil; + +/** + * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource. + * + *

+ * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length + * limit of 36 characters. + *

+ *

+ * regex: [a-z0-9\-\.]{1,36} + *

+ */ +@DatatypeDef(name = "id") +public class IdDt implements IPrimitiveDatatype { + + private String myBaseUrl; + private boolean myHaveComponentParts; + private String myResourceType; + private String myUnqualifiedId; + private String myUnqualifiedVersionId; + private volatile String myValue; + + /** + * Create a new empty ID + */ + public IdDt() { + super(); + } + + /** + * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation. + */ + public IdDt(BigDecimal thePid) { + if (thePid != null) { + setValue(toPlainStringWithNpeThrowIfNeeded(thePid)); + } else { + setValue(null); + } + } + + /** + * Create a new ID using a long + */ + public IdDt(long theId) { + setValue(Long.toString(theId)); + } + + /** + * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete URL (http://example.com/fhir/Patient/1234). + * + *

+ * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length + * limit of 36 characters. + *

+ *

+ * regex: [a-z0-9\-\.]{1,36} + *

+ */ + @SimpleSetter + public IdDt(@SimpleSetter.Parameter(name = "theId") String theValue) { + setValue(theValue); + } + + /** + * Constructor + * + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theId + * The ID (e.g. "123") + */ + public IdDt(String theResourceType, BigDecimal theIdPart) { + this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); + } + + /** + * Constructor + * + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theId + * The ID (e.g. "123") + */ + public IdDt(String theResourceType, String theId) { + this(theResourceType, theId, null); + } + + /** + * Constructor + * + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theId + * The ID (e.g. "123") + * @param theVersionId + * The version ID ("e.g. "456") + */ + public IdDt(String theResourceType, String theId, String theVersionId) { + this(null,theResourceType,theId,theVersionId); + } + + /** + * Constructor + * + * @param theBaseUrl + * The server base URL (e.g. "http://example.com/fhir") + * @param theResourceType + * The resource type (e.g. "Patient") + * @param theId + * The ID (e.g. "123") + * @param theVersionId + * The version ID ("e.g. "456") + */ + public IdDt(String theBaseUrl, String theResourceType, String theId, String theVersionId) { + myBaseUrl = theBaseUrl; + myResourceType = theResourceType; + myUnqualifiedId = theId; + myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null); + myHaveComponentParts = true; + } + + /** + * Creates an ID based on a given URL + */ + public IdDt(UriDt theUrl) { + setValue(theUrl.getValueAsString()); + } + + /** + * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous) + */ + public BigDecimal asBigDecimal() { + return getIdPartAsBigDecimal(); + } + + /** + * Returns true if this IdDt matches the given IdDt in terms of resource type and ID, but ignores the URL base + */ + @SuppressWarnings("deprecation") + public boolean equalsIgnoreBase(IdDt theId) { + if (theId == null) { + return false; + } + if (theId.isEmpty()) { + return isEmpty(); + } + return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart()); + } + + + + @Override + public boolean equals(Object theArg0) { + if (!(theArg0 instanceof IdDt)) { + return false; + } + IdDt id = (IdDt)theArg0; + return StringUtils.equals(getValueAsString(), id.getValueAsString()); + } + + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getValueAsString()); + return b.toHashCode(); + } + + /** + * Returns the portion of this resource ID which corresponds to the server base URL. For example given the resource ID http://example.com/fhir/Patient/123 the base URL would be + * http://example.com/fhir. + *

+ * This method may return null if the ID contains no base (e.g. "Patient/123") + *

+ */ + public String getBaseUrl() { + return myBaseUrl; + } + + public String getIdPart() { + return myUnqualifiedId; + } + + /** + * Returns the unqualified portion of this ID as a big decimal, or null if the value is null + * + * @throws NumberFormatException + * If the value is not a valid BigDecimal + */ + public BigDecimal getIdPartAsBigDecimal() { + String val = getIdPart(); + if (isBlank(val)) { + return null; + } + return new BigDecimal(val); + } + + /** + * Returns the unqualified portion of this ID as a {@link Long}, or null if the value is null + * + * @throws NumberFormatException + * If the value is not a valid Long + */ + public Long getIdPartAsLong() { + String val = getIdPart(); + if (isBlank(val)) { + return null; + } + return Long.parseLong(val); + } + + public String getResourceType() { + return myResourceType; + } + + /** + * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get just the ID portion. + * + * @see #getIdPart() + */ + @Override + public String getValue() { + if (myValue == null && myHaveComponentParts) { + StringBuilder b = new StringBuilder(); + if (isNotBlank(myBaseUrl)) { + b.append(myBaseUrl); + if (myBaseUrl.charAt(myBaseUrl.length()-1)!='/') { + b.append('/'); + } + } + + if (isNotBlank(myResourceType)) { + b.append(myResourceType); + } + + if (b.length() > 0) { + b.append('/'); + } + + b.append(myUnqualifiedId); + if (isNotBlank(myUnqualifiedVersionId)) { + b.append('/'); + b.append(Constants.PARAM_HISTORY); + b.append('/'); + b.append(myUnqualifiedVersionId); + } + myValue = b.toString(); + } + return myValue; + } + + @Override + public String getValueAsString() { + return getValue(); + } + + public String getVersionIdPart() { + return myUnqualifiedVersionId; + } + + public Long getVersionIdPartAsLong() { + if (!hasVersionIdPart()) { + return null; + } else { + return Long.parseLong(getVersionIdPart()); + } + } + + /** + * Returns true if this ID has a base url + * + * @see #getBaseUrl() + */ + public boolean hasBaseUrl() { + return isNotBlank(myBaseUrl); + } + + public boolean hasIdPart() { + return isNotBlank(getIdPart()); + } + + public boolean hasResourceType() { + return isNotBlank(myResourceType); + } + + public boolean hasVersionIdPart() { + return isNotBlank(getVersionIdPart()); + } + + /** + * Returns true if this ID contains an absolute URL (in other words, a URL starting with "http://" or "https://" + */ + public boolean isAbsolute() { + if (StringUtils.isBlank(getValue())) { + return false; + } + return UrlUtil.isAbsolute(getValue()); + } + + /** + * Returns true if the unqualified ID is a valid {@link Long} value (in other words, it consists only of digits) + */ + public boolean isIdPartValidLong() { + String id = getIdPart(); + if (StringUtils.isBlank(id)) { + return false; + } + for (int i = 0; i < id.length(); i++) { + if (Character.isDigit(id.charAt(i)) == false) { + return false; + } + } + return true; + } + + /** + * Returns true if the ID is a local reference (in other words, it begins with the '#' character) + */ + public boolean isLocal() { + return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#'; + } + + /** + * Copies the value from the given IdDt to this IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API. + */ + public void setId(IdDt theId) { + setValue(theId.getValue()); + } + + /** + * Set the value + * + *

+ * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length + * limit of 36 characters. + *

+ *

+ * regex: [a-z0-9\-\.]{1,36} + *

+ * @return + */ + @Override + public IdDt setValue(String theValue) throws DataFormatException { + // TODO: add validation + myValue = theValue; + myHaveComponentParts = false; + if (StringUtils.isBlank(theValue)) { + myValue = null; + myUnqualifiedId = null; + myUnqualifiedVersionId = null; + myResourceType = null; + } else { + int vidIndex = theValue.indexOf("/_history/"); + int idIndex; + if (vidIndex != -1) { + myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length()); + idIndex = theValue.lastIndexOf('/', vidIndex - 1); + myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex); + } else { + idIndex = theValue.lastIndexOf('/'); + myUnqualifiedId = theValue.substring(idIndex + 1); + myUnqualifiedVersionId = null; + } + + myBaseUrl = null; + if (idIndex <= 0) { + myResourceType = null; + } else { + int typeIndex = theValue.lastIndexOf('/', idIndex - 1); + if (typeIndex == -1) { + myResourceType = theValue.substring(0, idIndex); + } else { + myResourceType = theValue.substring(typeIndex + 1, idIndex); + + if (typeIndex > 4) { + myBaseUrl = theValue.substring(0, typeIndex); + } + + } + } + + } + return this; + } + + /** + * Set the value + * + *

+ * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length + * limit of 36 characters. + *

+ *

+ * regex: [a-z0-9\-\.]{1,36} + *

+ */ + @Override + public void setValueAsString(String theValue) throws DataFormatException { + setValue(theValue); + } + + @Override + public String toString() { + return getValue(); + } + + public IdDt toUnqualified() { + return new IdDt(getResourceType(), getIdPart(), getVersionIdPart()); + } + + public IdDt toUnqualifiedVersionless() { + return new IdDt(getResourceType(), getIdPart()); + } + + public IdDt toVersionless() { + String value = getValue(); + int i = value.indexOf(Constants.PARAM_HISTORY); + if (i > 1) { + return new IdDt(value.substring(0, i - 1)); + } else { + return this; + } + } + + public IdDt withResourceType(String theResourceName) { + return new IdDt(theResourceName, getIdPart(), getVersionIdPart()); + } + + /** + * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be used if the ID does not already contain those respective parts). Essentially, + * because IdDt can contain either a complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete URL. + * + * @param theServerBase + * The server base (e.g. "http://example.com/fhir") + * @param theResourceType + * The resource name (e.g. "Patient") + * @return A fully qualified URL for this ID (e.g. "http://example.com/fhir/Patient/1") + */ + public String withServerBase(String theServerBase, String theResourceType) { + if (getValue().startsWith("http")) { + return getValue(); + } + StringBuilder retVal = new StringBuilder(); + retVal.append(theServerBase); + if (retVal.charAt(retVal.length() - 1) != '/') { + retVal.append('/'); + } + if (isNotBlank(getResourceType())) { + retVal.append(getResourceType()); + } else { + retVal.append(theResourceType); + } + retVal.append('/'); + retVal.append(getIdPart()); + + if (hasVersionIdPart()) { + retVal.append('/'); + retVal.append(Constants.PARAM_HISTORY); + retVal.append('/'); + retVal.append(getVersionIdPart()); + } + + return retVal.toString(); + } + + /** + * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion. + * + * @param theVersion + * The actual version string, e.g. "1" + * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion. + */ + public IdDt withVersion(String theVersion) { + Validate.notBlank(theVersion, "Version may not be null or empty"); + + String existingValue = getValue(); + + int i = existingValue.indexOf(Constants.PARAM_HISTORY); + String value; + if (i > 1) { + value = existingValue.substring(0, i - 1); + } else { + value = existingValue; + } + + return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion); + } + + private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) { + if (theIdPart == null) { + throw new NullPointerException("BigDecimal ID can not be null"); + } + return theIdPart.toPlainString(); + } + + @Override + public boolean isEmpty() { + return isBlank(getValue()); + } + + public void applyTo(IBaseResource theResouce) { + if (theResouce == null) { + throw new NullPointerException("theResource can not be null"); + } else if (theResouce instanceof IResource) { + ((IResource) theResouce).setId(new IdDt(getValue())); + } else if (theResouce instanceof Resource) { + ((Resource) theResouce).setId(getIdPart()); + } else { + throw new IllegalArgumentException("Unknown resource class type, does not implement IResource or extend Resource"); + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleEntryStatusEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleEntryStatusEnum.java new file mode 100644 index 00000000000..bb162f926a5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleEntryStatusEnum.java @@ -0,0 +1,128 @@ + +package ca.uhn.fhir.model.valueset; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +import ca.uhn.fhir.model.api.IValueSetEnumBinder; + +public enum BundleEntryStatusEnum { + + CREATE("create", "http://hl7.org/fhir/bundle-entry-status"), + UPDATE("update", "http://hl7.org/fhir/bundle-entry-status"), + MATCH("match", "http://hl7.org/fhir/bundle-entry-status"), + INCLUDE("include", "http://hl7.org/fhir/bundle-entry-status"), + + ; + + /** + * Identifier for this Value Set: + * http://hl7.org/fhir/vs/address-use + */ + public static final String VALUESET_IDENTIFIER = "http://hl7.org/fhir/bundle-entry-status"; + + /** + * Name for this Value Set: + * AddressUse + */ + public static final String VALUESET_NAME = "BundleEntryStatus"; + + private static Map CODE_TO_ENUM = new HashMap(); + private static Map> SYSTEM_TO_CODE_TO_ENUM = new HashMap>(); + + private final String myCode; + private final String mySystem; + + static { + for (BundleEntryStatusEnum next : BundleEntryStatusEnum.values()) { + CODE_TO_ENUM.put(next.getCode(), next); + + if (!SYSTEM_TO_CODE_TO_ENUM.containsKey(next.getSystem())) { + SYSTEM_TO_CODE_TO_ENUM.put(next.getSystem(), new HashMap()); + } + SYSTEM_TO_CODE_TO_ENUM.get(next.getSystem()).put(next.getCode(), next); + } + } + + /** + * Returns the code associated with this enumerated value + */ + public String getCode() { + return myCode; + } + + /** + * Returns the code system associated with this enumerated value + */ + public String getSystem() { + return mySystem; + } + + /** + * Returns the enumerated value associated with this code + */ + public BundleEntryStatusEnum forCode(String theCode) { + BundleEntryStatusEnum retVal = CODE_TO_ENUM.get(theCode); + return retVal; + } + + /** + * Converts codes to their respective enumerated values + */ + public static final IValueSetEnumBinder VALUESET_BINDER = new IValueSetEnumBinder() { + @Override + public String toCodeString(BundleEntryStatusEnum theEnum) { + return theEnum.getCode(); + } + + @Override + public String toSystemString(BundleEntryStatusEnum theEnum) { + return theEnum.getSystem(); + } + + @Override + public BundleEntryStatusEnum fromCodeString(String theCodeString) { + return CODE_TO_ENUM.get(theCodeString); + } + + @Override + public BundleEntryStatusEnum fromCodeString(String theCodeString, String theSystemString) { + Map map = SYSTEM_TO_CODE_TO_ENUM.get(theSystemString); + if (map == null) { + return null; + } + return map.get(theCodeString); + } + + }; + + /** + * Constructor + */ + BundleEntryStatusEnum(String theCode, String theSystem) { + myCode = theCode; + mySystem = theSystem; + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java new file mode 100644 index 00000000000..4a1b4877abb --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java @@ -0,0 +1,138 @@ + +package ca.uhn.fhir.model.valueset; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +import ca.uhn.fhir.model.api.IValueSetEnumBinder; + +public enum BundleTypeEnum { + + TRANSACTION("transaction", "http://hl7.org/fhir/bundle-type"), + + DOCUMENT("document", "http://hl7.org/fhir/bundle-type"), + + MESSAGE("message", "http://hl7.org/fhir/bundle-type"), + + TRANSACTION_RESPONSE("transaction-response", "http://hl7.org/fhir/bundle-type"), + + HISTORY("history", "http://hl7.org/fhir/bundle-type"), + + SEARCHSET("searchset", "http://hl7.org/fhir/bundle-type"), + + COLLECTION("collection", "http://hl7.org/fhir/bundle-type"), + + + ; + + /** + * Identifier for this Value Set: + * http://hl7.org/fhir/vs/address-use + */ + public static final String VALUESET_IDENTIFIER = "http://hl7.org/fhir/bundle-type"; + + /** + * Name for this Value Set: + * AddressUse + */ + public static final String VALUESET_NAME = "BundleType"; + + private static Map CODE_TO_ENUM = new HashMap(); + private static Map> SYSTEM_TO_CODE_TO_ENUM = new HashMap>(); + + private final String myCode; + private final String mySystem; + + static { + for (BundleTypeEnum next : BundleTypeEnum.values()) { + CODE_TO_ENUM.put(next.getCode(), next); + + if (!SYSTEM_TO_CODE_TO_ENUM.containsKey(next.getSystem())) { + SYSTEM_TO_CODE_TO_ENUM.put(next.getSystem(), new HashMap()); + } + SYSTEM_TO_CODE_TO_ENUM.get(next.getSystem()).put(next.getCode(), next); + } + } + + /** + * Returns the code associated with this enumerated value + */ + public String getCode() { + return myCode; + } + + /** + * Returns the code system associated with this enumerated value + */ + public String getSystem() { + return mySystem; + } + + /** + * Returns the enumerated value associated with this code + */ + public BundleTypeEnum forCode(String theCode) { + BundleTypeEnum retVal = CODE_TO_ENUM.get(theCode); + return retVal; + } + + /** + * Converts codes to their respective enumerated values + */ + public static final IValueSetEnumBinder VALUESET_BINDER = new IValueSetEnumBinder() { + @Override + public String toCodeString(BundleTypeEnum theEnum) { + return theEnum.getCode(); + } + + @Override + public String toSystemString(BundleTypeEnum theEnum) { + return theEnum.getSystem(); + } + + @Override + public BundleTypeEnum fromCodeString(String theCodeString) { + return CODE_TO_ENUM.get(theCodeString); + } + + @Override + public BundleTypeEnum fromCodeString(String theCodeString, String theSystemString) { + Map map = SYSTEM_TO_CODE_TO_ENUM.get(theSystemString); + if (map == null) { + return null; + } + return map.get(theCodeString); + } + + }; + + /** + * Constructor + */ + BundleTypeEnum(String theCode, String theSystem) { + myCode = theCode; + mySystem = theSystem; + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index b573b494819..e045c597244 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -38,6 +38,7 @@ import java.util.Set; import javax.json.Json; import javax.json.JsonArray; +import javax.json.JsonNumber; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonString; @@ -59,6 +60,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; @@ -72,6 +74,7 @@ import ca.uhn.fhir.model.api.IIdentifiableElement; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.Child; @@ -82,6 +85,7 @@ import ca.uhn.fhir.model.dstu.resource.Binary; import ca.uhn.fhir.model.primitive.BooleanDt; import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; @@ -89,22 +93,31 @@ import ca.uhn.fhir.narrative.INarrativeGenerator; public class JsonParser extends BaseParser implements IParser { - private static final Set BUNDLE_TEXTNODE_CHILDREN; + private static final Set BUNDLE_TEXTNODE_CHILDREN_DSTU1; + private static final Set BUNDLE_TEXTNODE_CHILDREN_DSTU2; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class); static { - HashSet hashSet = new HashSet(); - hashSet.add("title"); - hashSet.add("id"); - hashSet.add("updated"); - hashSet.add("published"); - BUNDLE_TEXTNODE_CHILDREN = Collections.unmodifiableSet(hashSet); + HashSet hashSetDstu1 = new HashSet(); + hashSetDstu1.add("title"); + hashSetDstu1.add("id"); + hashSetDstu1.add("updated"); + hashSetDstu1.add("published"); + BUNDLE_TEXTNODE_CHILDREN_DSTU1 = Collections.unmodifiableSet(hashSetDstu1); + + HashSet hashSetDstu2 = new HashSet(); + hashSetDstu2.add("type"); + hashSetDstu2.add("base"); + hashSetDstu2.add("total"); + BUNDLE_TEXTNODE_CHILDREN_DSTU2 = Collections.unmodifiableSet(hashSetDstu2); } private FhirContext myContext; - private boolean myPrettyPrint; + /** + * Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke {@link FhirContext#newJsonParser()}. + */ public JsonParser(FhirContext theContext) { super(theContext); myContext = theContext; @@ -148,6 +161,15 @@ public class JsonParser extends BaseParser implements IParser { @Override public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException { JsonGenerator eventWriter = createJsonGenerator(theWriter); + if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + encodeBundleToWriterInDstu2Format(theBundle, eventWriter); + } else { + encodeBundleToWriterInDstu1Format(theBundle, eventWriter); + } + eventWriter.flush(); + } + + private void encodeBundleToWriterInDstu1Format(Bundle theBundle, JsonGenerator eventWriter) throws IOException { eventWriter.writeStartObject(); eventWriter.write("resourceType", "Bundle"); @@ -158,12 +180,12 @@ public class JsonParser extends BaseParser implements IParser { writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished()); boolean linkStarted = false; - linkStarted = writeAtomLink(eventWriter, "self", theBundle.getLinkSelf(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "first", theBundle.getLinkFirst(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "next", theBundle.getLinkNext(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "last", theBundle.getLinkLast(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "self", theBundle.getLinkSelf(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "first", theBundle.getLinkFirst(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "previous", theBundle.getLinkPrevious(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "next", theBundle.getLinkNext(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "last", theBundle.getLinkLast(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "fhir-base", theBundle.getLinkBase(), linkStarted); if (linkStarted) { eventWriter.writeEnd(); } @@ -186,9 +208,9 @@ public class JsonParser extends BaseParser implements IParser { writeTagWithTextNode(eventWriter, "id", nextEntry.getId()); linkStarted = false; - linkStarted = writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate(), linkStarted); - linkStarted = writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "self", nextEntry.getLinkSelf(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "alternate", nextEntry.getLinkAlternate(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(eventWriter, "search", nextEntry.getLinkSearch(), linkStarted); if (linkStarted) { eventWriter.writeEnd(); } @@ -215,24 +237,82 @@ public class JsonParser extends BaseParser implements IParser { eventWriter.writeEnd(); // entry array eventWriter.writeEnd(); - eventWriter.flush(); } - private void writeCategories(JsonGenerator eventWriter, TagList categories) { - if (categories != null && categories.size() > 0) { - eventWriter.writeStartArray("category"); - for (Tag next : categories) { - eventWriter.writeStartObject(); - eventWriter.write("term", defaultString(next.getTerm())); - eventWriter.write("label", defaultString(next.getLabel())); - eventWriter.write("scheme", defaultString(next.getScheme())); - eventWriter.writeEnd(); - } - eventWriter.writeEnd(); + private void encodeBundleToWriterInDstu2Format(Bundle theBundle, JsonGenerator theEventWriter) throws IOException { + theEventWriter.writeStartObject(); + + theEventWriter.write("resourceType", "Bundle"); + + writeTagWithTextNode(theEventWriter, "id", theBundle.getId().getIdPart()); + + theEventWriter.writeStartObject("meta"); + writeOptionalTagWithTextNode(theEventWriter, "versionId", theBundle.getId().getVersionIdPart()); + writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", theBundle.getUpdated()); + theEventWriter.writeEnd(); + + writeOptionalTagWithTextNode(theEventWriter, "type", theBundle.getType()); + writeOptionalTagWithTextNode(theEventWriter, "base", theBundle.getLinkBase()); + writeOptionalTagWithNumberNode(theEventWriter, "total", theBundle.getTotalResults()); + + boolean linkStarted = false; + linkStarted = writeAtomLinkInDstu2Format(theEventWriter, "next", theBundle.getLinkNext(), linkStarted); + linkStarted = writeAtomLinkInDstu2Format(theEventWriter, "self", theBundle.getLinkSelf(), linkStarted); + linkStarted = writeAtomLinkInDstu2Format(theEventWriter, "first", theBundle.getLinkFirst(), linkStarted); + linkStarted = writeAtomLinkInDstu2Format(theEventWriter, "previous", theBundle.getLinkPrevious(), linkStarted); + linkStarted = writeAtomLinkInDstu2Format(theEventWriter, "last", theBundle.getLinkLast(), linkStarted); + if (linkStarted) { + theEventWriter.writeEnd(); } + + theEventWriter.writeStartArray("entry"); + for (BundleEntry nextEntry : theBundle.getEntries()) { + theEventWriter.writeStartObject(); + + boolean deleted = nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false; + if (deleted) { + writeTagWithTextNode(theEventWriter, "deleted", nextEntry.getDeletedAt()); + } + + writeOptionalTagWithTextNode(theEventWriter, "base", nextEntry.getLinkBase()); + writeOptionalTagWithTextNode(theEventWriter, "status", nextEntry.getStatus()); + writeOptionalTagWithTextNode(theEventWriter, "search", nextEntry.getLinkSearch()); + writeOptionalTagWithDecimalNode(theEventWriter, "score", nextEntry.getScore()); + + linkStarted = false; + linkStarted = writeAtomLinkInDstu1Format(theEventWriter, "self", nextEntry.getLinkSelf(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(theEventWriter, "alternate", nextEntry.getLinkAlternate(), linkStarted); + linkStarted = writeAtomLinkInDstu1Format(theEventWriter, "search", nextEntry.getLinkSearch(), linkStarted); + if (linkStarted) { + theEventWriter.writeEnd(); + } + + writeOptionalTagWithTextNode(theEventWriter, "updated", nextEntry.getUpdated()); + writeOptionalTagWithTextNode(theEventWriter, "published", nextEntry.getPublished()); + + writeCategories(theEventWriter, nextEntry.getCategories()); + + writeAuthor(nextEntry, theEventWriter); + + IResource resource = nextEntry.getResource(); + if (resource != null && !resource.isEmpty() && !deleted) { + RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource); + encodeResourceToJsonStreamWriter(resDef, resource, theEventWriter, "resource", false); + } + + if (nextEntry.getSummary().isEmpty() == false) { + theEventWriter.write("summary", nextEntry.getSummary().getValueAsString()); + } + + theEventWriter.writeEnd(); // entry object + } + theEventWriter.writeEnd(); // entry array + + theEventWriter.writeEnd(); } - private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theWriter, IBase theNextValue, BaseRuntimeElementDefinition theChildDef, String theChildName, boolean theIsSubElementWithinResource) throws IOException { + private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theWriter, IBase theNextValue, + BaseRuntimeElementDefinition theChildDef, String theChildName, boolean theIsSubElementWithinResource) throws IOException { switch (theChildDef.getChildType()) { case PRIMITIVE_DATATYPE: { @@ -343,10 +423,11 @@ public class JsonParser extends BaseParser implements IParser { } - private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonGenerator theEventWriter, List theChildren, boolean theIsSubElementWithinResource) throws IOException { + private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonGenerator theEventWriter, + List theChildren, boolean theIsSubElementWithinResource) throws IOException { for (BaseRuntimeChildDefinition nextChild : theChildren) { if (nextChild instanceof RuntimeChildNarrativeDefinition) { - + INarrativeGenerator gen = myContext.getNarrativeGenerator(); if (gen != null) { NarrativeDt narr = gen.generateNarrative(theResDef.getResourceProfile(), theResource); @@ -468,18 +549,24 @@ public class JsonParser extends BaseParser implements IParser { } } - private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonGenerator theEventWriter, BaseRuntimeElementCompositeDefinition resDef, boolean theIsSubElementWithinResource) throws IOException, DataFormatException { + private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonGenerator theEventWriter, + BaseRuntimeElementCompositeDefinition resDef, boolean theIsSubElementWithinResource) throws IOException, DataFormatException { extractAndWriteExtensionsAsDirectChild(theNextValue, theEventWriter, resDef, theResDef, theResource); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, resDef.getExtensions(), theIsSubElementWithinResource); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, resDef.getChildren(), theIsSubElementWithinResource); } - private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theIsSubElementWithinResource) throws IOException { + private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, + boolean theIsSubElementWithinResource) throws IOException { String resourceId = null; if (theResource instanceof IResource) { IResource res = (IResource) theResource; - if (theIsSubElementWithinResource && StringUtils.isNotBlank(res.getId().getValue())) { - resourceId = res.getId().getValue(); + if (StringUtils.isNotBlank(res.getId().getIdPart())) { + if (theIsSubElementWithinResource) { + resourceId = res.getId().getIdPart(); + } else if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + resourceId = res.getId().getIdPart(); + } } } else if (theResource instanceof Resource) { Resource res = (Resource) theResource; @@ -491,7 +578,8 @@ public class JsonParser extends BaseParser implements IParser { encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theIsSubElementWithinResource, resourceId); } - private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theIsSubElementWithinResource, String theResourceId) throws IOException { + private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, + boolean theIsSubElementWithinResource, String theResourceId) throws IOException { if (!theIsSubElementWithinResource) { super.containResourcesForEncoding(theResource); } @@ -509,6 +597,14 @@ public class JsonParser extends BaseParser implements IParser { theEventWriter.write("id", theResourceId); } + if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1) && theResource instanceof IResource) { + IResource resource = (IResource) theResource; + theEventWriter.writeStartObject("meta"); + writeOptionalTagWithTextNode(theEventWriter, "versionId", resource.getId().getVersionIdPart()); + writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", ResourceMetadataKeyEnum.UPDATED.get(resource)); + theEventWriter.writeEnd(); + } + if (theResource instanceof Binary) { Binary bin = (Binary) theResource; theEventWriter.write("contentType", bin.getContentType()); @@ -561,10 +657,10 @@ public class JsonParser extends BaseParser implements IParser { } /** - * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object - * called _name): resource extensions, and extension extensions + * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object called _name): resource extensions, and extension extensions */ - private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition theElementDef, RuntimeResourceDefinition theResDef, IBaseResource theResource) throws IOException { + private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition theElementDef, RuntimeResourceDefinition theResDef, + IBaseResource theResource) throws IOException { List extensions = new ArrayList(0); List modifierExtensions = new ArrayList(0); @@ -661,7 +757,8 @@ public class JsonParser extends BaseParser implements IParser { object = reader.readObject(); } catch (JsonParsingException e) { if (e.getMessage().startsWith("Unexpected char 39")) { - throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); + throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e); } throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e); } @@ -673,7 +770,11 @@ public class JsonParser extends BaseParser implements IParser { } ParserState state = ParserState.getPreAtomInstance(myContext, theResourceType, true); - state.enteringNewElement(null, "feed"); + if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + state.enteringNewElement(null, "Bundle"); + } else { + state.enteringNewElement(null, "feed"); + } parseBundleChildren(object, state); @@ -688,18 +789,6 @@ public class JsonParser extends BaseParser implements IParser { for (String nextName : theObject.keySet()) { if ("resourceType".equals(nextName)) { continue; - } else if ("link".equals(nextName)) { - JsonArray entries = theObject.getJsonArray(nextName); - for (JsonValue jsonValue : entries) { - theState.enteringNewElement(null, "link"); - JsonObject linkObj = (JsonObject) jsonValue; - String rel = linkObj.getString("rel", null); - String href = linkObj.getString("href", null); - theState.attributeValue("rel", rel); - theState.attributeValue("href", href); - theState.endingElement(); - } - continue; } else if ("entry".equals(nextName)) { JsonArray entries = theObject.getJsonArray(nextName); for (JsonValue jsonValue : entries) { @@ -708,11 +797,60 @@ public class JsonParser extends BaseParser implements IParser { theState.endingElement(); } continue; - } else if (BUNDLE_TEXTNODE_CHILDREN.contains(nextName)) { - theState.enteringNewElement(null, nextName); - theState.string(theObject.getString(nextName, null)); - theState.endingElement(); - continue; + } else if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) { + if ("link".equals(nextName)) { + JsonArray entries = theObject.getJsonArray(nextName); + for (JsonValue jsonValue : entries) { + theState.enteringNewElement(null, "link"); + JsonObject linkObj = (JsonObject) jsonValue; + String rel = linkObj.getString("rel", null); + String href = linkObj.getString("href", null); + theState.attributeValue("rel", rel); + theState.attributeValue("href", href); + theState.endingElement(); + } + continue; + } else if (BUNDLE_TEXTNODE_CHILDREN_DSTU1.contains(nextName)) { + theState.enteringNewElement(null, nextName); + theState.string(theObject.getString(nextName, null)); + theState.endingElement(); + continue; + } + } else { + if ("link".equals(nextName)) { + JsonArray entries = theObject.getJsonArray(nextName); + for (JsonValue jsonValue : entries) { + theState.enteringNewElement(null, "link"); + JsonObject linkObj = (JsonObject) jsonValue; + String rel = linkObj.getString("relation", null); + String href = linkObj.getString("url", null); + theState.enteringNewElement(null, "relation"); + theState.attributeValue("value", rel); + theState.endingElement(); + theState.enteringNewElement(null, "url"); + theState.attributeValue("value", href); + theState.endingElement(); + theState.endingElement(); + } + continue; + } else if (BUNDLE_TEXTNODE_CHILDREN_DSTU2.contains(nextName)) { + theState.enteringNewElement(null, nextName); + // String obj = theObject.getString(nextName, null); + + JsonValue obj = theObject.get(nextName); + if (obj == null) { + theState.attributeValue("value", null); + } else if (obj instanceof JsonString) { + theState.attributeValue("value", theObject.getString(nextName, null)); + } else if (obj instanceof JsonNumber) { + theState.attributeValue("value", obj.toString()); + } else { + throw new DataFormatException("Unexpected JSON object for entry '" + nextName + "'"); + } + + theState.endingElement(); + continue; + } } JsonValue nextVal = theObject.get(nextName); @@ -728,7 +866,9 @@ public class JsonParser extends BaseParser implements IParser { continue; } else if ("id".equals(nextName)) { elementId = theObject.getString(nextName); - continue; + if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) { + continue; + } } else if ("_id".equals(nextName)) { // _id is incorrect, but some early examples in the FHIR spec used it elementId = theObject.getString(nextName); @@ -743,6 +883,9 @@ public class JsonParser extends BaseParser implements IParser { continue; } else if (nextName.charAt(0) == '_') { continue; + } else if (nextName.contains(":") && myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + JsonArray array = theObject.getJsonArray(nextName); + parseExtensionInDstu2Style(theState, nextName, array); } JsonValue nextVal = theObject.get(nextName); @@ -762,6 +905,7 @@ public class JsonParser extends BaseParser implements IParser { } } + private void parseChildren(ParserState theState, String theName, JsonValue theJsonVal, JsonValue theAlternateVal) { switch (theJsonVal.getValueType()) { case ARRAY: { @@ -818,9 +962,9 @@ public class JsonParser extends BaseParser implements IParser { } } - private void parseExtension(ParserState theState, JsonArray array, boolean theIsModifier) { - for (int i = 0; i < array.size(); i++) { - JsonObject nextExtObj = array.getJsonObject(i); + private void parseExtension(ParserState theState, JsonArray theValues, boolean theIsModifier) { + for (int i = 0; i < theValues.size(); i++) { + JsonObject nextExtObj = theValues.getJsonObject(i); String url = nextExtObj.getString("url"); theState.enteringNewElementExtension(null, url, theIsModifier); for (Iterator iter = nextExtObj.keySet().iterator(); iter.hasNext();) { @@ -841,6 +985,14 @@ public class JsonParser extends BaseParser implements IParser { theState.endingElement(); } } + + private void parseExtensionInDstu2Style(ParserState theState, String theUrl, JsonArray theValues) { + theState.enteringNewElementExtension(null, theUrl, false); + + + + theState.endingElement(); + } @Override public T parseResource(Class theResourceType, Reader theReader) { @@ -901,7 +1053,7 @@ public class JsonParser extends BaseParser implements IParser { return this; } - private boolean writeAtomLink(JsonGenerator theEventWriter, String theRel, StringDt theLink, boolean theStarted) { + private boolean writeAtomLinkInDstu1Format(JsonGenerator theEventWriter, String theRel, StringDt theLink, boolean theStarted) { boolean retVal = theStarted; if (isNotBlank(theLink.getValue())) { if (theStarted == false) { @@ -917,6 +1069,22 @@ public class JsonParser extends BaseParser implements IParser { return retVal; } + private boolean writeAtomLinkInDstu2Format(JsonGenerator theEventWriter, String theRel, StringDt theLink, boolean theStarted) { + boolean retVal = theStarted; + if (isNotBlank(theLink.getValue())) { + if (theStarted == false) { + theEventWriter.writeStartArray("link"); + retVal = true; + } + + theEventWriter.writeStartObject(); + theEventWriter.write("relation", theRel); + theEventWriter.write("url", theLink.getValue()); + theEventWriter.writeEnd(); + } + return retVal; + } + private void writeAuthor(BaseBundle theBundle, JsonGenerator eventWriter) { if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) { eventWriter.writeStartArray("author"); @@ -928,7 +1096,22 @@ public class JsonParser extends BaseParser implements IParser { } } - private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List extensions, List modifierExtensions) throws IOException { + private void writeCategories(JsonGenerator eventWriter, TagList categories) { + if (categories != null && categories.size() > 0) { + eventWriter.writeStartArray("category"); + for (Tag next : categories) { + eventWriter.writeStartObject(); + eventWriter.write("term", defaultString(next.getTerm())); + eventWriter.write("label", defaultString(next.getLabel())); + eventWriter.write("scheme", defaultString(next.getScheme())); + eventWriter.writeEnd(); + } + eventWriter.writeEnd(); + } + } + + private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List extensions, + List modifierExtensions) throws IOException { if (extensions.isEmpty() == false) { theEventWriter.writeStartArray("extension"); for (HeldExtension next : extensions) { @@ -945,10 +1128,29 @@ public class JsonParser extends BaseParser implements IParser { } } - private void writeOptionalTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IPrimitiveDatatype theInstantDt) { - String str = theInstantDt.getValueAsString(); - if (StringUtils.isNotBlank(str)) { - theEventWriter.write(theElementName, theInstantDt.getValueAsString()); + private void writeOptionalTagWithNumberNode(JsonGenerator theEventWriter, String theElementName, IntegerDt theValue) { + if (theValue != null && theValue.isEmpty() == false) { + theEventWriter.write(theElementName, theValue.getValue().intValue()); + } + } + + private void writeOptionalTagWithDecimalNode(JsonGenerator theEventWriter, String theElementName, DecimalDt theValue) { + if (theValue != null && theValue.isEmpty() == false) { + theEventWriter.write(theElementName, theValue.getValue()); + } + } + + private void writeOptionalTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IPrimitiveDatatype thePrimitive) { + if (thePrimitive == null) { + return; + } + String str = thePrimitive.getValueAsString(); + writeOptionalTagWithTextNode(theEventWriter, theElementName, str); + } + + private void writeOptionalTagWithTextNode(JsonGenerator theEventWriter, String theElementName, String theValue) { + if (StringUtils.isNotBlank(theValue)) { + theEventWriter.write(theElementName, theValue); } } @@ -960,6 +1162,14 @@ public class JsonParser extends BaseParser implements IParser { } } + private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, String theValue) { + if (theValue != null && !theValue.isEmpty()) { + theEventWriter.write(theElementName, theValue); + } else { + theEventWriter.writeNull(theElementName); + } + } + private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, StringDt theStringDt) { if (StringUtils.isNotBlank(theStringDt.getValue())) { theEventWriter.write(theElementName, theStringDt.getValue()); 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 e34d04b68b6..ee4728a8b07 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 @@ -41,6 +41,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeElemContainedResources; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; @@ -53,6 +54,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; 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.IIdentifiableElement; import ca.uhn.fhir.model.api.IPrimitiveDatatype; @@ -62,11 +64,13 @@ import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.Binary; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.IModelVisitor; @@ -161,7 +165,8 @@ class ParserState { } /** - * Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically intended for embedded XHTML content + * Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically + * intended for embedded XHTML content */ public void xmlEvent(XMLEvent theNextEvent) { myState.xmlEvent(theNextEvent); @@ -169,7 +174,11 @@ class ParserState { public static ParserState getPreAtomInstance(FhirContext theContext, Class theResourceType, boolean theJsonMode) throws DataFormatException { ParserState retVal = new ParserState(theContext, theJsonMode); - retVal.push(retVal.new PreAtomState(theResourceType)); + if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) { + retVal.push(retVal.new PreAtomState(theResourceType)); + } else { + retVal.push(retVal.new PreBundleState(theResourceType)); + } return retVal; } @@ -244,7 +253,8 @@ class ParserState { myScheme = theValue; } else if ("value".equals(theName)) { /* - * This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values instead of one like everything else. + * This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values + * instead of one like everything else. */ switch (myCatState) { case STATE_LABEL: @@ -292,6 +302,33 @@ class ParserState { } + public class AtomDeletedEntryByState extends BaseState { + + private BundleEntry myEntry; + + public AtomDeletedEntryByState(BundleEntry theEntry) { + super(null); + myEntry = theEntry; + } + + @Override + public void endingElement() throws DataFormatException { + pop(); + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("name".equals(theLocalPart)) { + push(new AtomPrimitiveState(myEntry.getDeletedByName())); + } else if ("email".equals(theLocalPart)) { + push(new AtomPrimitiveState(myEntry.getDeletedByEmail())); + } else { + throw new DataFormatException("Unexpected element in entry: " + theLocalPart); + } + } + + } + public class AtomDeletedEntryState extends AtomEntryState { public AtomDeletedEntryState(Bundle theInstance, Class theResourceType) { @@ -307,6 +344,12 @@ class ParserState { } } + @Override + public void endingElement() throws DataFormatException { + putPlacerResourceInDeletedEntry(getEntry()); + super.endingElement(); + } + @Override public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { if ("by".equals(theLocalPart) && verifyNamespace(XmlParser.TOMBSTONES_NS, theNamespaceURI)) { @@ -318,39 +361,6 @@ class ParserState { } } - @Override - public void endingElement() throws DataFormatException { - putPlacerResourceInDeletedEntry(getEntry()); - super.endingElement(); - } - - } - - public class AtomDeletedEntryByState extends BaseState { - - private BundleEntry myEntry; - - public AtomDeletedEntryByState(BundleEntry theEntry) { - super(null); - myEntry = theEntry; - } - - @Override - public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { - if ("name".equals(theLocalPart)) { - push(new AtomPrimitiveState(myEntry.getDeletedByName())); - } else if ("email".equals(theLocalPart)) { - push(new AtomPrimitiveState(myEntry.getDeletedByEmail())); - } else { - throw new DataFormatException("Unexpected element in entry: " + theLocalPart); - } - } - - @Override - public void endingElement() throws DataFormatException { - pop(); - } - } private class AtomDeletedJsonWhenState extends BaseState { @@ -657,6 +667,72 @@ class ParserState { } + private class BasePreAtomOrBundleState extends BaseState { + + private Bundle myInstance; + + private Class myResourceType; + + public BasePreAtomOrBundleState(Class theResourceType) { + super(null); + myResourceType = theResourceType; + } + + @Override + public void endingElement() throws DataFormatException { + // ignore + } + + @Override + protected IElement getCurrentElement() { + return myInstance; + } + + public Bundle getInstance() { + return myInstance; + } + + protected Class getResourceType() { + return myResourceType; + } + + public void setInstance(Bundle theInstance) { + myInstance = theInstance; + } + + @SuppressWarnings("unchecked") + @Override + public void wereBack() { + myObject = (T) myInstance; + + /* + * Stitch together resource references + */ + + Map idToResource = new HashMap(); + List resources = myInstance.toListOfResources(); + for (IResource next : resources) { + if (next.getId() != null && next.getId().isEmpty() == false) { + idToResource.put(next.getId().toUnqualifiedVersionless().getValue(), next); + } + } + + for (IResource next : resources) { + List refs = myContext.newTerser().getAllPopulatedChildElementsOfType(next, ResourceReferenceDt.class); + for (ResourceReferenceDt nextRef : refs) { + if (nextRef.isEmpty() == false && nextRef.getReference() != null) { + IResource target = idToResource.get(nextRef.getReference().getValue()); + if (target != null) { + nextRef.setResource(target); + } + } + } + } + + } + + } + private abstract class BaseState { private PreResourceState myPreResourceState; @@ -812,6 +888,303 @@ class ParserState { } + private class BundleEntryDeletedState extends BaseState { + + private BundleEntry myEntry; + + public BundleEntryDeletedState(PreResourceState thePreResourceState, BundleEntry theEntry) { + super(thePreResourceState); + myEntry = theEntry; + } + + @Override + public void endingElement() throws DataFormatException { + String resType = myEntry.getDeletedResourceType().getValue(); + String id = myEntry.getDeletedResourceId().getValue(); + String version = myEntry.getDeletedResourceVersion().getValue(); + myEntry.setLinkSelf(new StringDt(new IdDt(resType, id, version).getValue())); + putPlacerResourceInDeletedEntry(myEntry); + pop(); + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("type".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getDeletedResourceType())); + } else if ("id".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getDeletedResourceId())); + } else if ("version".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getDeletedResourceVersion())); + } else if ("instant".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getDeletedAt())); + } else { + throw new DataFormatException("Unexpected element '" + theLocalPart + "' in element 'deleted'"); + } + } + + } + + public class BundleEntryState extends BaseState { + + private BundleEntry myEntry; + private Class myResourceType; + + public BundleEntryState(Bundle theInstance, Class theResourceType) { + super(null); + myEntry = new BundleEntry(); + myResourceType = theResourceType; + theInstance.getEntries().add(myEntry); + } + + @Override + public void endingElement() throws DataFormatException { + populateResourceMetadata(); + pop(); + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("base".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getLinkBase())); + } else if ("status".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getStatus())); + } else if ("search".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getLinkSearch())); + } else if ("score".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myEntry.getScore())); + } else if ("resource".equals(theLocalPart)) { + push(new PreResourceState(myEntry, myResourceType)); + } else if ("deleted".equals(theLocalPart)) { + push(new BundleEntryDeletedState(getPreResourceState(), myEntry)); + } else { + throw new DataFormatException("Unexpected element in entry: " + theLocalPart); + } + + // TODO: handle category + } + + protected BundleEntry getEntry() { + return myEntry; + } + + @SuppressWarnings("deprecation") + private void populateResourceMetadata() { + if (myEntry.getResource() == null) { + return; + } + + IdDt id = myEntry.getId(); + if (id != null && id.isEmpty() == false) { + myEntry.getResource().setId(id); + } + + Map, Object> metadata = myEntry.getResource().getResourceMetadata(); + if (myEntry.getPublished().isEmpty() == false) { + ResourceMetadataKeyEnum.PUBLISHED.put(myEntry.getResource(), myEntry.getPublished()); + } + if (myEntry.getUpdated().isEmpty() == false) { + ResourceMetadataKeyEnum.UPDATED.put(myEntry.getResource(), myEntry.getUpdated()); + } + + ResourceMetadataKeyEnum.TITLE.put(myEntry.getResource(), myEntry.getTitle().getValue()); + + if (myEntry.getCategories().isEmpty() == false) { + TagList tagList = new TagList(); + for (Tag next : myEntry.getCategories()) { + tagList.add(next); + } + ResourceMetadataKeyEnum.TAG_LIST.put(myEntry.getResource(), tagList); + } + if (!myEntry.getLinkSelf().isEmpty()) { + String linkSelfValue = myEntry.getLinkSelf().getValue(); + IdDt linkSelf = new IdDt(linkSelfValue); + myEntry.getResource().setId(linkSelf); + if (isNotBlank(linkSelf.getVersionIdPart())) { + metadata.put(ResourceMetadataKeyEnum.VERSION_ID, linkSelf); + } + } + if (!myEntry.getLinkAlternate().isEmpty()) { + ResourceMetadataKeyEnum.LINK_ALTERNATE.put(myEntry.getResource(), myEntry.getLinkAlternate().getValue()); + } + if (!myEntry.getLinkSearch().isEmpty()) { + ResourceMetadataKeyEnum.LINK_SEARCH.put(myEntry.getResource(), myEntry.getLinkSearch().getValue()); + } + + } + + } + + private class BundleLinkState extends BaseState { + + private BundleEntry myEntry; + private String myHref; + private Bundle myInstance; + private String myRel; + private boolean myInRelation = false; + private boolean myInUrl = false; + + public BundleLinkState(Bundle theInstance) { + super(null); + myInstance = theInstance; + } + + public BundleLinkState(BundleEntry theEntry) { + super(null); + myEntry = theEntry; + } + + @Override + public void attributeValue(String theName, String theValue) throws DataFormatException { + if (myInRelation) { + myRel = theValue; + } else if (myInUrl) { + myHref = theValue; + } + } + + @Override + public void endingElement() throws DataFormatException { + if (!myInRelation && !myInUrl) { + if (myInstance != null) { + if ("self".equals(myRel)) { + myInstance.getLinkSelf().setValueAsString(myHref); + } else if ("first".equals(myRel)) { + myInstance.getLinkFirst().setValueAsString(myHref); + } else if ("previous".equals(myRel)) { + myInstance.getLinkPrevious().setValueAsString(myHref); + } else if ("next".equals(myRel)) { + myInstance.getLinkNext().setValueAsString(myHref); + } else if ("last".equals(myRel)) { + myInstance.getLinkLast().setValueAsString(myHref); + } else if ("fhir-base".equals(myRel)) { + myInstance.getLinkBase().setValueAsString(myHref); + } + } else { + if ("self".equals(myRel)) { + myEntry.getLinkSelf().setValueAsString(myHref); + } else if ("search".equals(myRel)) { + myEntry.getLinkSearch().setValueAsString(myHref); + } else if ("alternate".equals(myRel)) { + myEntry.getLinkAlternate().setValueAsString(myHref); + } + } + pop(); + } else { + myInRelation = false; + myInUrl = false; + } + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if (myInRelation || myInUrl) { + throw new DataFormatException("Unexpected element '" + theLocalPart + "' in element 'link'"); + } + if ("relation".equals(theLocalPart)) { + myInRelation = true; + } else if ("url".equals(theLocalPart)) { + myInUrl = true; + } else { + throw new DataFormatException("Unexpected element '" + theLocalPart + "' in element 'link'"); + } + } + + } + + private class BundleState extends BaseState { + + private Bundle myInstance; + private Class myResourceType; + + public BundleState(Bundle theInstance, Class theResourceType) { + super(null); + myInstance = theInstance; + myResourceType = theResourceType; + } + + @Override + public void endingElement() throws DataFormatException { + pop(); + } + + @Override + public void wereBack() { + for (BundleEntry nextEntry : myInstance.getEntries()) { + IResource nextResource = nextEntry.getResource(); + + String baseUrl = myInstance.getLinkBase().getValue(); + String version = ResourceMetadataKeyEnum.VERSION.get(nextResource); + String resourceName = myContext.getResourceDefinition(nextResource).getName(); + nextResource.setId(new IdDt(baseUrl, resourceName, nextResource.getId().getIdPart(), version)); + } + + String bundleVersion = (String) myInstance.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION); + String baseUrl = myInstance.getLinkBase().getValue(); + String id = myInstance.getId().getIdPart(); + myInstance.setId(new IdDt(baseUrl, "Bundle", id, bundleVersion)); + + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if ("id".equals(theLocalPart)) { + push(new PrimitiveState(null, myInstance.getId())); + } else if ("meta".equals(theLocalPart)) { + push(new MetaElementState(null, myInstance.getResourceMetadata())); + } else if ("type".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myInstance.getType())); + } else if ("base".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myInstance.getLinkBase())); + } else if ("total".equals(theLocalPart)) { + push(new PrimitiveState(getPreResourceState(), myInstance.getTotalResults())); + } else if ("link".equals(theLocalPart)) { + push(new BundleLinkState(myInstance)); + } else if ("entry".equals(theLocalPart)) { + push(new BundleEntryState(myInstance, myResourceType)); + } else { + throw new DataFormatException("Unxpected element '" + theLocalPart + " in element 'Bundle'"); + } + + // if ("entry".equals(theLocalPart) && verifyNamespace(XmlParser.ATOM_NS, theNamespaceURI)) { + // push(new AtomEntryState(myInstance, myResourceType)); + // } else if (theLocalPart.equals("published")) { + // push(new AtomPrimitiveState(myInstance.getPublished())); + // } else if (theLocalPart.equals("title")) { + // push(new AtomPrimitiveState(myInstance.getTitle())); + // } else if ("id".equals(theLocalPart)) { + // push(new AtomPrimitiveState(myInstance.getBundleId())); + // } else if ("link".equals(theLocalPart)) { + // push(new AtomLinkState(myInstance)); + // } else if ("totalResults".equals(theLocalPart) && (verifyNamespace(XmlParser.OPENSEARCH_NS, + // theNamespaceURI) || verifyNamespace(Constants.OPENSEARCH_NS_OLDER, theNamespaceURI))) { + // push(new AtomPrimitiveState(myInstance.getTotalResults())); + // } else if ("updated".equals(theLocalPart)) { + // push(new AtomPrimitiveState(myInstance.getUpdated())); + // } else if ("author".equals(theLocalPart)) { + // push(new AtomAuthorState(myInstance)); + // } else if ("category".equals(theLocalPart)) { + // push(new AtomCategoryState(myInstance.getCategories())); + // } else if ("deleted-entry".equals(theLocalPart) && verifyNamespace(XmlParser.TOMBSTONES_NS, + // theNamespaceURI)) { + // push(new AtomDeletedEntryState(myInstance, myResourceType)); + // } else { + // if (theNamespaceURI != null) { + // throw new DataFormatException("Unexpected element: {" + theNamespaceURI + "}" + theLocalPart); + // } else { + // throw new DataFormatException("Unexpected element: " + theLocalPart); + // } + // } + + // TODO: handle category and DSig + } + + @Override + protected IElement getCurrentElement() { + return myInstance; + } + + } + private class ContainedResourcesState extends PreResourceState { public ContainedResourcesState(PreResourceState thePreResourcesState) { @@ -924,12 +1297,12 @@ class ParserState { } - private class ElementCompositeState extends BaseState { + private class ElementCompositeState extends BaseState { private BaseRuntimeElementCompositeDefinition myDefinition; - private IBase myInstance; + private T2 myInstance; - public ElementCompositeState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition theDef, IBase theInstance) { + public ElementCompositeState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition theDef, T2 theInstance) { super(thePreResourceState); myDefinition = theDef; myInstance = theInstance; @@ -982,7 +1355,7 @@ class ParserState { BaseRuntimeElementCompositeDefinition compositeTarget = (BaseRuntimeElementCompositeDefinition) target; ICompositeDatatype newChildInstance = (ICompositeDatatype) compositeTarget.newInstance(child.getInstanceConstructorArguments()); child.getMutator().addValue(myInstance, newChildInstance); - ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance); + ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance); push(newState); return; } @@ -1008,7 +1381,7 @@ class ParserState { RuntimeResourceBlockDefinition blockTarget = (RuntimeResourceBlockDefinition) target; IResourceBlock newBlockInstance = blockTarget.newInstance(); child.getMutator().addValue(myInstance, newBlockInstance); - ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), blockTarget, newBlockInstance); + ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), blockTarget, newBlockInstance); push(newState); return; } @@ -1057,7 +1430,7 @@ class ParserState { } @Override - protected IBase getCurrentElement() { + protected T2 getCurrentElement() { return myInstance; } @@ -1128,14 +1501,64 @@ class ParserState { } - private class PreAtomState extends BaseState { + private class MetaElementState extends BaseState { + private ResourceMetadataMap myMap; - private Bundle myInstance; - private Class myResourceType; + public MetaElementState(ParserState.PreResourceState thePreResourceState, ResourceMetadataMap theMap) { + super(thePreResourceState); + myMap = theMap; + } + + @Override + public void endingElement() throws DataFormatException { + pop(); + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if (theLocalPart.equals("versionId")) { + push(new MetaVersionElementState(getPreResourceState(), myMap)); + } else if (theLocalPart.equals("lastUpdated")) { + InstantDt updated = new InstantDt(); + push(new PrimitiveState(getPreResourceState(), updated)); + myMap.put(ResourceMetadataKeyEnum.UPDATED, updated); + } else { + throw new DataFormatException("Unexpected element '" + theLocalPart + "' found in 'meta' element"); + } + } + + } + + private class MetaVersionElementState extends BaseState { + + private ResourceMetadataMap myMap; + + public MetaVersionElementState(ParserState.PreResourceState thePreResourceState, ResourceMetadataMap theMap) { + super(thePreResourceState); + myMap = theMap; + } + + @Override + public void attributeValue(String theName, String theValue) throws DataFormatException { + myMap.put(ResourceMetadataKeyEnum.VERSION, theValue); + } + + @Override + public void endingElement() throws DataFormatException { + pop(); + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + throw new DataFormatException("Unexpected child element '" + theLocalPart + "' in element 'meta'"); + } + + } + + private class PreAtomState extends BasePreAtomOrBundleState { public PreAtomState(Class theResourceType) { - super(null); - myResourceType = theResourceType; + super(theResourceType); } @Override @@ -1149,49 +1572,59 @@ class ParserState { throw new DataFormatException("Expecting outer element called 'feed', found: " + theLocalPart); } - myInstance = new Bundle(); - push(new AtomState(myInstance, myResourceType)); - - } - - @Override - protected IElement getCurrentElement() { - return myInstance; - } - - @SuppressWarnings("unchecked") - @Override - public void wereBack() { - myObject = (T) myInstance; - - /* - * Stitch together resource references - */ - - Map idToResource = new HashMap(); - List resources = myInstance.toListOfResources(); - for (IResource next : resources) { - if (next.getId() != null && next.getId().isEmpty() == false) { - idToResource.put(next.getId().toUnqualifiedVersionless().getValue(), next); - } - } - - for (IBaseResource next : resources) { - List refs = myContext.newTerser().getAllPopulatedChildElementsOfType(next, ResourceReferenceDt.class); - for (ResourceReferenceDt nextRef : refs) { - if (nextRef.isEmpty() == false && nextRef.getReference() != null) { - IResource target = idToResource.get(nextRef.getReference().getValue()); - if (target != null) { - nextRef.setResource(target); - } - } - } - } + setInstance(new Bundle()); + push(new AtomState(getInstance(), getResourceType())); } } + private class PreBundleState extends BasePreAtomOrBundleState { + + public PreBundleState(Class theResourceType) { + super(theResourceType); + } + + @Override + public void endingElement() throws DataFormatException { + // ignore + } + + @Override + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + if (!"Bundle".equals(theLocalPart)) { + throw new DataFormatException("Expecting outer element called 'Bundle', found: " + theLocalPart); + } + + setInstance(new Bundle()); + push(new BundleState(getInstance(), getResourceType())); + + } + + } + + private class ResourceState extends ElementCompositeState + { + + public ResourceState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition theDef, IResource theInstance) { + super(thePreResourceState, theDef, theInstance); + } + + @Override + public void enteringNewElement(String theNamespace, String theChildName) throws DataFormatException { + if ("id".equals(theChildName)) { + push(new PrimitiveState(getPreResourceState(), getCurrentElement().getId())); + } else if ("meta".equals(theChildName)) { + push(new MetaElementState(getPreResourceState(), getCurrentElement().getResourceMetadata())); + }else { + super.enteringNewElement(theNamespace, theChildName); + } + } + + + } + + private class PreResourceState extends BaseState { private Map myContainedResources = new HashMap(); @@ -1248,10 +1681,11 @@ class ParserState { myEntry.setResource(myInstance); } - if ("Binary".equals(def.getName())) { + String resourceName = def.getName(); + if ("Binary".equals(resourceName)) { push(new BinaryResourceState(getRootPreResourceState(), (Binary) myInstance)); } else { - push(new ElementCompositeState(getRootPreResourceState(), def, myInstance)); + push(new ResourceState(getRootPreResourceState(), def, myInstance)); } } @@ -1290,12 +1724,6 @@ class ParserState { myContext.newTerser().visit(myInstance, new IModelVisitor() { - @Override - public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, - ExtensionDt theNextExt) { - acceptElement(theNextExt.getValue(), null, null); - } - @Override public void acceptElement(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { if (theElement instanceof ResourceReferenceDt) { @@ -1313,6 +1741,11 @@ class ParserState { } } } + + @Override + public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { + acceptElement(theNextExt.getValue(), null, null); + } }); } @@ -1504,12 +1937,12 @@ class ParserState { } @Override - public void enteringNewElementExtension(StartElement theElement, String theUrlAttr, boolean theIsModifier) { + public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { myDepth++; } @Override - public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException { + public void enteringNewElementExtension(StartElement theElement, String theUrlAttr, boolean theIsModifier) { myDepth++; } 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 dfd4b04f11c..6dc871c46f3 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 @@ -56,6 +56,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; @@ -67,6 +68,7 @@ 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.ISupportsUndeclaredExtensions; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.dstu.composite.ContainedDt; @@ -97,6 +99,10 @@ public class XmlParser extends BaseParser implements IParser { private FhirContext myContext; private boolean myPrettyPrint; + /** + * Do not use this constructor, the recommended way to obtain a new instance of the + * XML parser is to invoke {@link FhirContext#newXmlParser()}. + */ public XmlParser(FhirContext theContext) { super(theContext); myContext = theContext; @@ -114,118 +120,213 @@ public class XmlParser extends BaseParser implements IParser { public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { try { XMLStreamWriter eventWriter = createXmlWriter(theWriter); - - eventWriter.writeStartElement("feed"); - eventWriter.writeDefaultNamespace(ATOM_NS); - - writeTagWithTextNode(eventWriter, "title", theBundle.getTitle()); - writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId()); - - writeAtomLink(eventWriter, "self", theBundle.getLinkSelf()); - writeAtomLink(eventWriter, "first", theBundle.getLinkFirst()); - writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious()); - writeAtomLink(eventWriter, "next", theBundle.getLinkNext()); - writeAtomLink(eventWriter, "last", theBundle.getLinkLast()); - writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase()); - - if (theBundle.getTotalResults().getValue() != null) { - eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS); - eventWriter.writeNamespace("os", OPENSEARCH_NS); - eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString()); - eventWriter.writeEndElement(); + if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + encodeBundleToWriterUsingBundleResource(theBundle, eventWriter); + } else { + encodeBundleToWriterUsingAtom(theBundle, eventWriter); } - - writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated()); - writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished()); - - if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) { - eventWriter.writeStartElement("author"); - writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName()); - writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri()); - eventWriter.writeEndElement(); - } - - writeCategories(eventWriter, theBundle.getCategories()); - - for (BundleEntry nextEntry : theBundle.getEntries()) { - boolean deleted = false; - if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) { - deleted = true; - eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS); - eventWriter.writeNamespace("at", TOMBSTONES_NS); - eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString()); - eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString()); - if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) { - eventWriter.writeStartElement(TOMBSTONES_NS, "by"); - if (nextEntry.getDeletedByName().isEmpty() == false) { - eventWriter.writeStartElement(TOMBSTONES_NS, "name"); - eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue()); - eventWriter.writeEndElement(); - } - if (nextEntry.getDeletedByEmail().isEmpty() == false) { - eventWriter.writeStartElement(TOMBSTONES_NS, "email"); - eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue()); - eventWriter.writeEndElement(); - } - eventWriter.writeEndElement(); - } - if (nextEntry.getDeletedComment().isEmpty() == false) { - eventWriter.writeStartElement(TOMBSTONES_NS, "comment"); - eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue()); - eventWriter.writeEndElement(); - } - } else { - eventWriter.writeStartElement("entry"); - } - - writeOptionalTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); - if (!deleted) { - writeTagWithTextNode(eventWriter, "id", nextEntry.getId()); - } - writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated()); - writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished()); - - writeCategories(eventWriter, nextEntry.getCategories()); - - if (!nextEntry.getLinkSelf().isEmpty()) { - writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); - } - - if (!nextEntry.getLinkAlternate().isEmpty()) { - writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate()); - } - - if (!nextEntry.getLinkSearch().isEmpty()) { - writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch()); - } - - IResource resource = nextEntry.getResource(); - if (resource != null && !resource.isEmpty() && !deleted) { - eventWriter.writeStartElement("content"); - eventWriter.writeAttribute("type", "text/xml"); - encodeResourceToXmlStreamWriter(resource, eventWriter, false); - eventWriter.writeEndElement(); // content - } else { - ourLog.debug("Bundle entry contains null resource"); - } - - if (!nextEntry.getSummary().isEmpty()) { - eventWriter.writeStartElement("summary"); - eventWriter.writeAttribute("type", "xhtml"); - encodeXhtml(nextEntry.getSummary(), eventWriter); - eventWriter.writeEndElement(); - } - - eventWriter.writeEndElement(); // entry - } - - eventWriter.writeEndElement(); - eventWriter.close(); } catch (XMLStreamException e) { throw new ConfigurationException("Failed to initialize STaX event factory", e); } } + private void encodeBundleToWriterUsingAtom(Bundle theBundle, XMLStreamWriter eventWriter) throws XMLStreamException { + eventWriter.writeStartElement("feed"); + eventWriter.writeDefaultNamespace(ATOM_NS); + + writeTagWithTextNode(eventWriter, "title", theBundle.getTitle()); + writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId()); + + writeAtomLink(eventWriter, "self", theBundle.getLinkSelf()); + writeAtomLink(eventWriter, "first", theBundle.getLinkFirst()); + writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious()); + writeAtomLink(eventWriter, "next", theBundle.getLinkNext()); + writeAtomLink(eventWriter, "last", theBundle.getLinkLast()); + writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase()); + + if (theBundle.getTotalResults().getValue() != null) { + eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS); + eventWriter.writeNamespace("os", OPENSEARCH_NS); + eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString()); + eventWriter.writeEndElement(); + } + + writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated()); + writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished()); + + if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) { + eventWriter.writeStartElement("author"); + writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName()); + writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri()); + eventWriter.writeEndElement(); + } + + writeCategories(eventWriter, theBundle.getCategories()); + + for (BundleEntry nextEntry : theBundle.getEntries()) { + boolean deleted = false; + if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) { + deleted = true; + eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS); + eventWriter.writeNamespace("at", TOMBSTONES_NS); + eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString()); + eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString()); + if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "by"); + if (nextEntry.getDeletedByName().isEmpty() == false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "name"); + eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue()); + eventWriter.writeEndElement(); + } + if (nextEntry.getDeletedByEmail().isEmpty() == false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "email"); + eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue()); + eventWriter.writeEndElement(); + } + eventWriter.writeEndElement(); + } + if (nextEntry.getDeletedComment().isEmpty() == false) { + eventWriter.writeStartElement(TOMBSTONES_NS, "comment"); + eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue()); + eventWriter.writeEndElement(); + } + } else { + eventWriter.writeStartElement("entry"); + } + + writeOptionalTagWithTextNode(eventWriter, "title", nextEntry.getTitle()); + if (!deleted) { + writeTagWithTextNode(eventWriter, "id", nextEntry.getId()); + } + writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated()); + writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished()); + + writeCategories(eventWriter, nextEntry.getCategories()); + + if (!nextEntry.getLinkSelf().isEmpty()) { + writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf()); + } + + if (!nextEntry.getLinkAlternate().isEmpty()) { + writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate()); + } + + if (!nextEntry.getLinkSearch().isEmpty()) { + writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch()); + } + + IResource resource = nextEntry.getResource(); + if (resource != null && !resource.isEmpty() && !deleted) { + eventWriter.writeStartElement("content"); + eventWriter.writeAttribute("type", "text/xml"); + encodeResourceToXmlStreamWriter(resource, eventWriter, false); + eventWriter.writeEndElement(); // content + } else { + ourLog.debug("Bundle entry contains null resource"); + } + + if (!nextEntry.getSummary().isEmpty()) { + eventWriter.writeStartElement("summary"); + eventWriter.writeAttribute("type", "xhtml"); + encodeXhtml(nextEntry.getSummary(), eventWriter); + eventWriter.writeEndElement(); + } + + eventWriter.writeEndElement(); // entry + } + + eventWriter.writeEndElement(); + eventWriter.close(); + } + + private void encodeBundleToWriterUsingBundleResource(Bundle theBundle, XMLStreamWriter theEventWriter) throws XMLStreamException { + theEventWriter.writeStartElement("Bundle"); + theEventWriter.writeDefaultNamespace(FHIR_NS); + + writeOptionalTagWithValue(theEventWriter, "id", theBundle.getId().getIdPart()); + + theEventWriter.writeStartElement("meta"); + writeOptionalTagWithValue(theEventWriter, "versionId", theBundle.getId().getVersionIdPart()); + InstantDt updated = (InstantDt) theBundle.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + if (updated != null) { + writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); + } + theEventWriter.writeEndElement(); + + String bundleBaseUrl = theBundle.getLinkBase().getValue(); + + writeOptionalTagWithValue(theEventWriter, "type", theBundle.getType().getValue()); + writeOptionalTagWithValue(theEventWriter, "base", bundleBaseUrl); + writeOptionalTagWithValue(theEventWriter, "total", theBundle.getTotalResults().getValueAsString()); + + writeBundleResourceLink(theEventWriter, "first", theBundle.getLinkFirst()); + writeBundleResourceLink(theEventWriter, "previous", theBundle.getLinkPrevious()); + writeBundleResourceLink(theEventWriter, "next", theBundle.getLinkNext()); + writeBundleResourceLink(theEventWriter, "last", theBundle.getLinkLast()); + writeBundleResourceLink(theEventWriter, "self", theBundle.getLinkSelf()); + + for (BundleEntry nextEntry : theBundle.getEntries()) { + theEventWriter.writeStartElement("entry"); + + IResource nextResource = nextEntry.getResource(); + if (nextResource.getId() != null && nextResource.getId().hasBaseUrl()) { + if (!nextResource.getId().getBaseUrl().equals(bundleBaseUrl)) { + writeOptionalTagWithValue(theEventWriter, "base", bundleBaseUrl); + } + } + + writeOptionalTagWithValue(theEventWriter, "status", nextEntry.getStatus().getValue()); + writeOptionalTagWithValue(theEventWriter, "search", nextEntry.getLinkSearch().getValue()); + writeOptionalTagWithValue(theEventWriter, "score", nextEntry.getScore().getValueAsString()); + + boolean deleted = false; + if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) { + deleted = true; + theEventWriter.writeStartElement("deleted"); + writeOptionalTagWithValue(theEventWriter, "type", nextEntry.getId().getResourceType()); + writeOptionalTagWithValue(theEventWriter, "id", nextEntry.getId().getIdPart()); + writeOptionalTagWithValue(theEventWriter, "versionId", nextEntry.getId().getVersionIdPart()); + writeOptionalTagWithValue(theEventWriter, "instant", nextEntry.getDeletedAt().getValueAsString()); + theEventWriter.writeEndElement(); + } + + IResource resource = nextEntry.getResource(); + if (resource != null && !resource.isEmpty() && !deleted) { + theEventWriter.writeStartElement("resource"); + encodeResourceToXmlStreamWriter(resource, theEventWriter, false); + theEventWriter.writeEndElement(); // content + } else { + ourLog.debug("Bundle entry contains null resource"); + } + + theEventWriter.writeEndElement(); // entry + } + + theEventWriter.writeEndElement(); + theEventWriter.close(); + } + + private void writeBundleResourceLink(XMLStreamWriter theEventWriter, String theRel, StringDt theUrl) throws XMLStreamException { + if (theUrl.isEmpty()==false) { + theEventWriter.writeStartElement("link"); + theEventWriter.writeStartElement("relation"); + theEventWriter.writeAttribute("value", theRel); + theEventWriter.writeEndElement(); + theEventWriter.writeStartElement("url"); + theEventWriter.writeAttribute("value", theUrl.getValue()); + theEventWriter.writeEndElement(); + theEventWriter.writeEndElement(); + } + } + + private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException { + if (StringUtils.isNotBlank(theValue)) { + theEventWriter.writeStartElement(theName); + theEventWriter.writeAttribute("value", theValue); + theEventWriter.writeEndElement(); + } + } + private void writeCategories(XMLStreamWriter eventWriter, TagList categories) throws XMLStreamException { if (categories != null) { for (Tag next : categories) { @@ -619,6 +720,7 @@ public class XmlParser extends BaseParser implements IParser { resourceId = resource.getId(); } } + encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId); } @@ -634,10 +736,25 @@ public class XmlParser extends BaseParser implements IParser { theEventWriter.writeStartElement(resDef.getName()); theEventWriter.writeDefaultNamespace(FHIR_NS); - - if (theResourceId != null) { - theEventWriter.writeAttribute("id", theResourceId); + + if (!myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + if (theResourceId != null) { + theEventWriter.writeAttribute("id", theResourceId); + } + } else { + + IResource resource = (IResource) theResource; + writeOptionalTagWithValue(theEventWriter, "id", resource.getId().getIdPart()); + + theEventWriter.writeStartElement("meta"); + writeOptionalTagWithValue(theEventWriter, "versionId", resource.getId().getVersionIdPart()); + InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + if (updated != null) { + writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); + } + theEventWriter.writeEndElement(); } + if (theResource instanceof Binary) { Binary bin = (Binary) theResource; diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index c80efae7f53..c854115a561 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -1,4 +1,7 @@ +ca.uhn.fhir.context.FhirContext.noStructures=Could not find any HAPI-FHIR structure JARs on the classpath. Note that as of HAPI-FHIR v0.8, a separate FHIR strcture JAR must be added to your classpath (or project pom.xml if you are using Maven) +ca.uhn.fhir.context.FhirContext.noStructuresForSpecifiedVersion=Could not find the HAPI-FHIR structure JAR on the classpath for version {0}. Note that as of HAPI-FHIR v0.8, a separate FHIR strcture JAR must be added to your classpath (or project pom.xml if you are using Maven) + ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread=No version specified in URL for 'vread' operation: {0} ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead=The given URI is not an absolute URL and is not usable for this operation: {0} ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri=Unable to determine the resource type from the given URI: {0} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml index 993f90aa2da..4b63662cd3d 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-tester-config.xml @@ -19,6 +19,7 @@ blaze , Blaze (Orion Health) , https://fhir.orionhealth.com/blaze/fhir oridashi , Oridashi , http://demo.oridashi.com.au:8190 + nortal , Nortal , http://fhir.nortal.com/fhir-server diff --git a/hapi-fhir-structures-dev/.classpath b/hapi-fhir-structures-dev/.classpath index f3e655204ba..e10c4a412b2 100644 --- a/hapi-fhir-structures-dev/.classpath +++ b/hapi-fhir-structures-dev/.classpath @@ -6,6 +6,7 @@ + diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java index de6f79ef724..5b19cdd968d 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/FhirDev.java @@ -20,8 +20,12 @@ package ca.uhn.fhir.model.dev; * #L% */ +import java.io.InputStream; + import org.apache.commons.lang3.StringUtils; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IResource; @@ -61,4 +65,21 @@ public class FhirDev implements IFhirVersion { return new ServerProfileProvider(theRestfulServer.getFhirContext()); } + @Override + public FhirVersionEnum getVersion() { + return FhirVersionEnum.DEV; + } + + @Override + public InputStream getFhirVersionPropertiesFile() { + InputStream str = FhirDev.class.getResourceAsStream("/ca/uhn/fhir/model/dev/fhirversion.properties"); + if (str == null) { + str = FhirDev.class.getResourceAsStream("ca/uhn/fhir/model/dev/fhirversion.properties"); + } + if (str == null) { + throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dev/model.properties"); + } + return str; + } + } diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/AgeDt.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/AgeDt.java index 8cddc638ad5..502963d0710 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/AgeDt.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/AgeDt.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.dev.composite; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + /* * #%L * HAPI FHIR Structures - DEV (FHIR Latest) @@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dev.composite; * #L% */ +@DatatypeDef(name="AgeDt") public class AgeDt extends QuantityDt { } diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/CountDt.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/CountDt.java index 878c35de5e1..8968174fddf 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/CountDt.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/CountDt.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.dev.composite; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + /* * #%L * HAPI FHIR Structures - DEV (FHIR Latest) @@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dev.composite; * #L% */ +@DatatypeDef(name="CountDt") public class CountDt extends QuantityDt { } diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DistanceDt.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DistanceDt.java index 51afb6e26ef..f91e01486b1 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DistanceDt.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DistanceDt.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.dev.composite; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + /* * #%L * HAPI FHIR Structures - DEV (FHIR Latest) @@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dev.composite; * #L% */ +@DatatypeDef(name="DistanceDt") public class DistanceDt extends QuantityDt { } diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DurationDt.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DurationDt.java index 4a2dbd8997a..9f1e673d819 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DurationDt.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/DurationDt.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.dev.composite; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + /* * #%L * HAPI FHIR Structures - DEV (FHIR Latest) @@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dev.composite; * #L% */ +@DatatypeDef(name="DurationDt") public class DurationDt extends QuantityDt { } diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/MoneyDt.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/MoneyDt.java index 9c9fbf9cf75..7a57a323cd3 100644 --- a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/MoneyDt.java +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dev/composite/MoneyDt.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.model.dev.composite; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + /* * #%L * HAPI FHIR Structures - DEV (FHIR Latest) @@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dev.composite; * #L% */ +@DatatypeDef(name="MoneyDt") public class MoneyDt extends QuantityDt { } diff --git a/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java new file mode 100644 index 00000000000..67f877aee38 --- /dev/null +++ b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -0,0 +1,138 @@ +package ca.uhn.fhir.parser; + +import static org.junit.Assert.*; +import net.sf.json.JSON; +import net.sf.json.JSONSerializer; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.dev.resource.MedicationPrescription; +import ca.uhn.fhir.model.dev.resource.Patient; +import ca.uhn.fhir.model.primitive.CodeDt; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.InstantDt; + +public class JsonParserTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserTest.class); + private static final FhirContext ourCtx = FhirContext.forDev(); + + @Test + public void testParseBundleWithBinary() { + // TODO: implement this test, make sure we handle ID and meta correctly in Binary + } + + @Test + public void testParseAndEncodeBundle() throws Exception { + String content = IOUtils.toString(JsonParserTest.class.getResourceAsStream("/bundle-example.json")); + + Bundle parsed = ourCtx.newJsonParser().parseBundle(content); + assertEquals("http://example.com/base/Bundle/example/_history/1", parsed.getId().getValue()); + assertEquals("1", parsed.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION)); + assertEquals("1", parsed.getId().getVersionIdPart()); + assertEquals(new InstantDt("2014-08-18T01:43:30Z"), parsed.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED)); + assertEquals("transaction", parsed.getType().getValue()); + assertEquals(3, parsed.getTotalResults().getValue().intValue()); + assertEquals("http://example.com/base", parsed.getLinkBase().getValue()); + assertEquals("https://example.com/base/MedicationPrescription?patient=347&searchId=ff15fd40-ff71-4b48-b366-09c706bed9d0&page=2", parsed.getLinkNext().getValue()); + assertEquals("https://example.com/base/MedicationPrescription?patient=347", parsed.getLinkSelf().getValue()); + + assertEquals(1, parsed.getEntries().size()); + assertEquals("update", parsed.getEntries().get(0).getStatus().getValue()); + + MedicationPrescription p = (MedicationPrescription) parsed.getEntries().get(0).getResource(); + assertEquals("Patient/example", p.getPatient().getReference().getValue()); + assertEquals("2014-08-16T05:31:17Z", ResourceMetadataKeyEnum.UPDATED.get(p).getValueAsString()); + assertEquals("http://example.com/base/MedicationPrescription/3123/_history/1", p.getId().getValue()); + + String reencoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeBundleToString(parsed); + ourLog.info(reencoded); + + JSON expected = JSONSerializer.toJSON(content.trim()); + JSON actual = JSONSerializer.toJSON(reencoded.trim()); + + String exp = expected.toString().replace("\\r\\n", "\\n"); // .replace("§", "§"); + String act = actual.toString().replace("\\r\\n", "\\n"); + + ourLog.info("Expected: {}", exp); + ourLog.info("Actual : {}", act); + + assertEquals(exp, act); + + } + + @Test + public void testParseAndEncodeNewExtensionFormat() { + //@formatter:off + String resource = "{\n" + + " \"resourceType\" : \"Patient\",\n" + + " \"http://acme.org/fhir/ExtensionDefinition/trial-status\" : [{\n" + + " \"code\" : [{ \"valueCode\" : \"unsure\" }],\n" + + " \"date\" : [{ \"valueDate\" : \"2009-03-14\" }], \n" + + " \"registrar\" : [{ \"valueReference\" : {\n" + + " \"reference\" : \"Practitioner/example\"\n" + + " }\n" + + " }]\n" + + " }],\n" + + " \"gender\" : \"M\",\n" + + " \"name\" : [{\n" + + " \"family\": [\n" + + " \"du\",\n" + + " \"Marché\"\n" + + " ],\n" + + " \"_family\": [\n" + + " {\n" + + " \"http://hl7.org/fhir/ExtensionDefinition/iso21090-EN-qualifier\": [\n" + + " {\n" + + " \"valueCode\": \"VV\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " null\n" + + " ],\n" + + " \"given\": [\n" + + " \"Bénédicte\"\n" + + " ]\n" + + " }]" + + "}"; + //@formatter:on + + ourLog.info(resource); + + Patient parsed = ourCtx.newJsonParser().parseResource(Patient.class, resource); + + // Gender + assertEquals("M",parsed.getGender()); + assertEquals(1, parsed.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/trial-status").size()); + + // Trial status + ExtensionDt ext = parsed.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/trial-status").get(0); + assertNull(ext.getValue()); + assertEquals(1, ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/code").size()); + assertEquals(CodeDt.class, ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/code").get(0).getValue().getClass()); + assertEquals("unsure", ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/code").get(0).getValueAsPrimitive().getValueAsString()); + assertEquals(1, ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/date").size()); + assertEquals(DateDt.class, ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/date").get(0).getValue().getClass()); + assertEquals("2009-03-14", ext.getUndeclaredExtensionsByUrl("http://acme.org/fhir/ExtensionDefinition/date").get(0).getValueAsPrimitive().getValueAsString()); + + // Name + assertEquals(1, parsed.getName().size()); + assertEquals(2, parsed.getName().get(0).getFamily().size()); + assertEquals("du Marché", parsed.getName().get(0).getFamilyAsSingleString()); + assertEquals(1, parsed.getName().get(0).getGiven().size()); + assertEquals("Bénédicte", parsed.getName().get(0).getGivenAsSingleString()); + + // Patient.name[0].family extensions + assertEquals(1, parsed.getNameFirstRep().getFamily().get(0).getUndeclaredExtensionsByUrl("http://hl7.org/fhir/ExtensionDefinition/iso21090-EN-qualifier").size()); + assertEquals(CodeDt.class, parsed.getNameFirstRep().getFamily().get(0).getUndeclaredExtensionsByUrl("http://hl7.org/fhir/ExtensionDefinition/iso21090-EN-qualifier").get(0).getValueAsPrimitive().getClass()); + assertEquals("VV", parsed.getNameFirstRep().getFamily().get(0).getUndeclaredExtensionsByUrl("http://hl7.org/fhir/ExtensionDefinition/iso21090-EN-qualifier").get(0).getValueAsPrimitive().getValueAsString()); + + } + + + +} diff --git a/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java new file mode 100644 index 00000000000..6ac82139959 --- /dev/null +++ b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java @@ -0,0 +1,66 @@ +package ca.uhn.fhir.parser; + +import static org.junit.Assert.*; + +import java.io.StringReader; + +import org.apache.commons.io.IOUtils; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.dev.resource.MedicationPrescription; +import ca.uhn.fhir.model.primitive.InstantDt; + +public class XmlParserTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserTest.class); + private static final FhirContext ourCtx = FhirContext.forDev(); + + @BeforeClass + public static void beforeClass() { + XMLUnit.setIgnoreAttributeOrder(true); + XMLUnit.setIgnoreComments(true); + XMLUnit.setIgnoreWhitespace(true); + } + + @Test + public void testParseBundleWithBinary() { + // TODO: implement this test, make sure we handle ID and meta correctly in Binary + } + + @Test + public void testParseAndEncodeBundle() throws Exception { + String content = IOUtils.toString(XmlParserTest.class.getResourceAsStream("/bundle-example(example).xml")); + + Bundle parsed = ourCtx.newXmlParser().parseBundle(content); + assertEquals("http://example.com/base/Bundle/example/_history/1", parsed.getId().getValue()); + assertEquals("1", parsed.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION)); + assertEquals("1", parsed.getId().getVersionIdPart()); + assertEquals(new InstantDt("2014-08-18T01:43:30Z"), parsed.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED)); + assertEquals("transaction", parsed.getType().getValue()); + assertEquals(3, parsed.getTotalResults().getValue().intValue()); + assertEquals("http://example.com/base", parsed.getLinkBase().getValue()); + assertEquals("https://example.com/base/MedicationPrescription?patient=347&searchId=ff15fd40-ff71-4b48-b366-09c706bed9d0&page=2", parsed.getLinkNext().getValue()); + assertEquals("https://example.com/base/MedicationPrescription?patient=347", parsed.getLinkSelf().getValue()); + + assertEquals(1, parsed.getEntries().size()); + assertEquals("update", parsed.getEntries().get(0).getStatus().getValue()); + + MedicationPrescription p = (MedicationPrescription) parsed.getEntries().get(0).getResource(); + assertEquals("Patient/example", p.getPatient().getReference().getValue()); + assertEquals("2014-08-16T05:31:17Z", ResourceMetadataKeyEnum.UPDATED.get(p).getValueAsString()); + assertEquals("http://example.com/base/MedicationPrescription/3123/_history/1", p.getId().getValue()); + + String reencoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(parsed); + ourLog.info(reencoded); + + Diff d = new Diff(new StringReader(content), new StringReader(reencoded)); + assertTrue(d.toString(), d.identical()); + + } + +} diff --git a/hapi-fhir-structures-dev/src/test/resources/bundle-example(example).xml b/hapi-fhir-structures-dev/src/test/resources/bundle-example(example).xml new file mode 100644 index 00000000000..2969fd1b367 --- /dev/null +++ b/hapi-fhir-structures-dev/src/test/resources/bundle-example(example).xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Penicillin VK 5ml suspension to be administered by oral route

+

ONE 5ml spoonful to be taken THREE times a day

+

100ml bottle

+

to patient ref: a23

+

by doctor X

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/hapi-fhir-structures-dev/src/test/resources/bundle-example.json b/hapi-fhir-structures-dev/src/test/resources/bundle-example.json new file mode 100644 index 00000000000..301599b2f6e --- /dev/null +++ b/hapi-fhir-structures-dev/src/test/resources/bundle-example.json @@ -0,0 +1,82 @@ +{ + "resourceType": "Bundle", + "id": "example", + "meta": { + "versionId": "1", + "lastUpdated": "2014-08-18T01:43:30Z" + }, + "type": "transaction", + "base": "http://example.com/base", + "total": 3, + "link": [ + { + "relation": "next", + "url": "https://example.com/base/MedicationPrescription?patient=347&searchId=ff15fd40-ff71-4b48-b366-09c706bed9d0&page=2" + }, + { + "relation": "self", + "url": "https://example.com/base/MedicationPrescription?patient=347" + } + ], + "entry": [ + { + "status": "update", + "resource": { + "resourceType": "MedicationPrescription", + "id": "3123", + "meta": { + "versionId": "1", + "lastUpdated": "2014-08-16T05:31:17Z" + }, + "text": { + "status": "generated", + "div": "
\n

Penicillin VK 5ml suspension to be administered by oral route

\n

ONE 5ml spoonful to be taken THREE times a day

\n

100ml bottle

\n

to patient ref: a23

\n

by doctor X

\n
" + }, + "status": "active", + "patient": { + "reference": "Patient/example" + }, + "prescriber": { + "reference": "Practitioner/example" + }, + "medication": { + "reference": "Medication/example" + }, + "dosageInstruction": [ + { + "scheduledTiming": { + "repeat": { + "frequency": 3, + "duration": 1, + "units": "d" + } + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "394899003", + "display": "oral administration of treatment" + } + ] + }, + "doseQuantity": { + "value": 5, + "units": "ml", + "system": "http://unitsofmeasure.org", + "code": "ml" + } + } + ], + "dispense": { + "quantity": { + "value": 100, + "units": "ml", + "system": "http://unitsofmeasure.org", + "code": "ml" + } + } + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java index 2b7cfafc0d1..4bf5a3145b2 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java @@ -20,9 +20,9 @@ package ca.uhn.fhir.model.dstu; * #L% */ -import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.StringUtils.join; +import static org.apache.commons.lang3.StringUtils.*; +import java.io.InputStream; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -31,14 +31,31 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.model.api.ICompositeDatatype; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.hl7.fhir.instance.model.IBaseResource; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeChildContainedResources; +import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceBlockDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimeCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeResourceBlockDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceReferenceDefinition; +import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; @@ -322,5 +339,25 @@ public class FhirDstu1 implements IFhirVersion { public IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer) { return new ServerProfileProvider(theRestfulServer.getFhirContext()); } + + @Override + public FhirVersionEnum getVersion() { + return FhirVersionEnum.DSTU1; + } + + @Override + public InputStream getFhirVersionPropertiesFile() { + InputStream str = FhirDstu1.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/fhirversion.properties"); + if (str == null) { + str = FhirDstu1.class.getResourceAsStream("ca/uhn/fhir/model/dstu/fhirversion.properties"); + } + if (str == null) { + throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties"); + } + return str; + } + + + } diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java.orig b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java.orig new file mode 100644 index 00000000000..3f599906d5a --- /dev/null +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java.orig @@ -0,0 +1,369 @@ +package ca.uhn.fhir.model.dstu; + +/* + * #%L + * HAPI FHIR Structures - DSTU (FHIR 0.80) + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static org.apache.commons.lang3.StringUtils.*; + +import java.io.InputStream; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +<<<<<<< HEAD +import ca.uhn.fhir.context.*; +import ca.uhn.fhir.model.api.ICompositeDatatype; + +======= +>>>>>>> c294e1c064fcbf112edcbf4e10c341691c12a1a8 +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.text.WordUtils; +import org.hl7.fhir.instance.model.IBaseResource; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeChildContainedResources; +import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceBlockDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimeCompositeDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimeResourceBlockDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceReferenceDefinition; +import ca.uhn.fhir.model.api.ICompositeDatatype; +import ca.uhn.fhir.model.api.IFhirVersion; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.dstu.resource.Profile; +import ca.uhn.fhir.model.dstu.resource.Profile.ExtensionDefn; +import ca.uhn.fhir.model.dstu.resource.Profile.Structure; +import ca.uhn.fhir.model.dstu.resource.Profile.StructureElement; +import ca.uhn.fhir.model.dstu.resource.Profile.StructureElementDefinitionType; +import ca.uhn.fhir.model.dstu.valueset.DataTypeEnum; +import ca.uhn.fhir.model.dstu.valueset.SlicingRulesEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider; +import ca.uhn.fhir.rest.server.provider.ServerProfileProvider; + +public class FhirDstu1 implements IFhirVersion { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirDstu1.class); +// private Map myExtensionDefToCode = new HashMap(); + private String myId; + + @Override + public Object createServerConformanceProvider(RestfulServer theServer) { + return new ServerConformanceProvider(theServer); + } + + private void fillBasics(StructureElement theElement, BaseRuntimeElementDefinition def, LinkedList path, BaseRuntimeDeclaredChildDefinition theChild) { + if (path.isEmpty()) { + path.add(def.getName()); + theElement.setName(def.getName()); + } else { + path.add(WordUtils.uncapitalize(theChild.getElementName())); + theElement.setName(theChild.getElementName()); + } + theElement.setPath(StringUtils.join(path, '.')); + } + + private void fillExtensions(Structure theStruct, LinkedList path, List extList, String elementName, boolean theIsModifier) { + if (extList.size() > 0) { + StructureElement extSlice = theStruct.addElement(); + extSlice.setName(elementName); + extSlice.setPath(join(path, '.') + '.' + elementName); + extSlice.getSlicing().getDiscriminator().setValue("url"); + extSlice.getSlicing().setOrdered(false); + extSlice.getSlicing().setRules(SlicingRulesEnum.OPEN); + extSlice.getDefinition().addType().setCode(DataTypeEnum.EXTENSION); + + for (RuntimeChildDeclaredExtensionDefinition nextExt : extList) { + StructureElement nextProfileExt = theStruct.addElement(); + nextProfileExt.getDefinition().setIsModifier(theIsModifier); + nextProfileExt.setName(extSlice.getName()); + nextProfileExt.setPath(extSlice.getPath()); + fillMinAndMaxAndDefinitions(nextExt, nextProfileExt); + StructureElementDefinitionType type = nextProfileExt.getDefinition().addType(); + type.setCode(DataTypeEnum.EXTENSION); + if (nextExt.isDefinedLocally()) { + type.setProfile(nextExt.getExtensionUrl().substring(nextExt.getExtensionUrl().indexOf('#'))); + } else { + type.setProfile(nextExt.getExtensionUrl()); + } + } + } else { + StructureElement extSlice = theStruct.addElement(); + extSlice.setName(elementName); + extSlice.setPath(join(path, '.') + '.' + elementName); + extSlice.getDefinition().setIsModifier(theIsModifier); + extSlice.getDefinition().addType().setCode(DataTypeEnum.EXTENSION); + extSlice.getDefinition().setMin(0); + extSlice.getDefinition().setMax("*"); + } + } + + private void fillMinAndMaxAndDefinitions(BaseRuntimeDeclaredChildDefinition child, StructureElement elem) { + elem.getDefinition().setMin(child.getMin()); + if (child.getMax() == Child.MAX_UNLIMITED) { + elem.getDefinition().setMax("*"); + } else { + elem.getDefinition().setMax(Integer.toString(child.getMax())); + } + + if (isNotBlank(child.getShortDefinition())) { + elem.getDefinition().getShort().setValue(child.getShortDefinition()); + } + if (isNotBlank(child.getFormalDefinition())) { + elem.getDefinition().getFormal().setValue(child.getFormalDefinition()); + } + } + + private void fillName(StructureElement elem, BaseRuntimeElementDefinition nextDef) { + if (nextDef instanceof RuntimeResourceReferenceDefinition) { + RuntimeResourceReferenceDefinition rr = (RuntimeResourceReferenceDefinition) nextDef; + for (Class next : rr.getResourceTypes()) { + StructureElementDefinitionType type = elem.getDefinition().addType(); + type.getCode().setValue("ResourceReference"); + + if (next != IResource.class) { + @SuppressWarnings("unchecked") + RuntimeResourceDefinition resDef = rr.getDefinitionForResourceType((Class) next); + type.getProfile().setValueAsString(resDef.getResourceProfile()); + } + } + + return; + } + + StructureElementDefinitionType type = elem.getDefinition().addType(); + String name = nextDef.getName(); + DataTypeEnum fromCodeString = DataTypeEnum.VALUESET_BINDER.fromCodeString(name); + if (fromCodeString == null) { + throw new ConfigurationException("Unknown type: " + name); + } + type.setCode(fromCodeString); + } + + private void fillProfile(Structure theStruct, StructureElement theElement, BaseRuntimeElementDefinition def, LinkedList path, BaseRuntimeDeclaredChildDefinition theChild) { + + fillBasics(theElement, def, path, theChild); + + String expectedPath = StringUtils.join(path, '.'); + + ourLog.debug("Filling profile for: {} - Path: {}", expectedPath); + String name = def.getName(); + if (!expectedPath.equals(name)) { + path.pollLast(); + theElement.getDefinition().getNameReference().setValue(def.getName()); + return; + } + + fillExtensions(theStruct, path, def.getExtensionsNonModifier(), "extension", false); + fillExtensions(theStruct, path, def.getExtensionsModifier(), "modifierExtension", true); + + if (def.getChildType() == ChildTypeEnum.RESOURCE) { + StructureElement narrative = theStruct.addElement(); + narrative.setName("text"); + narrative.setPath(join(path, '.') + ".text"); + narrative.getDefinition().addType().setCode(DataTypeEnum.NARRATIVE); + narrative.getDefinition().setIsModifier(false); + narrative.getDefinition().setMin(0); + narrative.getDefinition().setMax("1"); + + StructureElement contained = theStruct.addElement(); + contained.setName("contained"); + contained.setPath(join(path, '.') + ".contained"); + contained.getDefinition().addType().getCode().setValue("Resource"); + contained.getDefinition().setIsModifier(false); + contained.getDefinition().setMin(0); + contained.getDefinition().setMax("1"); + } + + if (def instanceof BaseRuntimeElementCompositeDefinition) { + BaseRuntimeElementCompositeDefinition cdef = ((BaseRuntimeElementCompositeDefinition) def); + for (BaseRuntimeChildDefinition nextChild : cdef.getChildren()) { + if (nextChild instanceof RuntimeChildUndeclaredExtensionDefinition) { + continue; + } + + BaseRuntimeDeclaredChildDefinition child = (BaseRuntimeDeclaredChildDefinition) nextChild; + StructureElement elem = theStruct.addElement(); + fillMinAndMaxAndDefinitions(child, elem); + + if (child instanceof RuntimeChildResourceBlockDefinition) { + RuntimeResourceBlockDefinition nextDef = (RuntimeResourceBlockDefinition) child.getSingleChildOrThrow(); + fillProfile(theStruct, elem, nextDef, path, child); + } else if (child instanceof RuntimeChildContainedResources) { + // ignore + } else if (child instanceof RuntimeChildDeclaredExtensionDefinition) { + throw new IllegalStateException("Unexpected child type: " + child.getClass().getCanonicalName()); + } else if (child instanceof RuntimeChildCompositeDatatypeDefinition || child instanceof RuntimeChildPrimitiveDatatypeDefinition || child instanceof RuntimeChildChoiceDefinition + || child instanceof RuntimeChildResourceDefinition) { + Iterator childNamesIter = child.getValidChildNames().iterator(); + String nextName = childNamesIter.next(); + BaseRuntimeElementDefinition nextDef = child.getChildByName(nextName); + fillBasics(elem, nextDef, path, child); + fillName(elem, nextDef); + while (childNamesIter.hasNext()) { + nextDef = child.getChildByName(childNamesIter.next()); + fillName(elem, nextDef); + } + path.pollLast(); + } else { + throw new IllegalStateException("Unexpected child type: " + child.getClass().getCanonicalName()); + } + + } + } else { + throw new IllegalStateException("Unexpected child type: " + def.getClass().getCanonicalName()); + } + + path.pollLast(); + } + + @Override + public IResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition) { + Profile retVal = new Profile(); + + RuntimeResourceDefinition def = theRuntimeResourceDefinition; + + myId = def.getId(); + if (StringUtils.isBlank(myId)) { + myId = theRuntimeResourceDefinition.getName().toLowerCase(); + } + + retVal.setId(new IdDt(myId)); + + // Scan for extensions + scanForExtensions(retVal, def, new HashMap()); + Collections.sort(retVal.getExtensionDefn(), new Comparator() { + @Override + public int compare(ExtensionDefn theO1, ExtensionDefn theO2) { + return theO1.getCode().compareTo(theO2.getCode()); + } + }); + + // Scan for children + retVal.setName(def.getName()); + Structure struct = retVal.addStructure(); + LinkedList path = new LinkedList(); + + StructureElement element = struct.addElement(); + element.getDefinition().setMin(1); + element.getDefinition().setMax("1"); + + fillProfile(struct, element, def, path, null); + + retVal.getStructure().get(0).getElement().get(0).getDefinition().addType().getCode().setValue("Resource"); + + return retVal; + } + + private Map scanForExtensions(Profile theProfile, BaseRuntimeElementDefinition def, Map theExtensionDefToCode) { + BaseRuntimeElementCompositeDefinition cdef = ((BaseRuntimeElementCompositeDefinition) def); + + for (RuntimeChildDeclaredExtensionDefinition nextChild : cdef.getExtensions()) { + if (theExtensionDefToCode.containsKey(nextChild)) { + continue; + } + + if (nextChild.isDefinedLocally() == false) { + continue; + } + + ExtensionDefn defn = theProfile.addExtensionDefn(); + String code = null; + if (nextChild.getExtensionUrl().contains("#") && !nextChild.getExtensionUrl().endsWith("#")) { + code = nextChild.getExtensionUrl().substring(nextChild.getExtensionUrl().indexOf('#') + 1); + } else { + throw new ConfigurationException("Locally defined extension has no '#[code]' part in extension URL: " + nextChild.getExtensionUrl()); + } + + defn.setCode(code); + if (theExtensionDefToCode.values().contains(code)) { + throw new IllegalStateException("Duplicate extension code: " + code); + } + theExtensionDefToCode.put(nextChild, code); + + if (nextChild.getChildType() != null && IPrimitiveDatatype.class.isAssignableFrom(nextChild.getChildType())) { + RuntimePrimitiveDatatypeDefinition pdef = (RuntimePrimitiveDatatypeDefinition) nextChild.getSingleChildOrThrow(); + defn.getDefinition().addType().setCode(DataTypeEnum.VALUESET_BINDER.fromCodeString(pdef.getName())); + } else if (nextChild.getChildType() != null && ICompositeDatatype.class.isAssignableFrom(nextChild.getChildType())) { + RuntimeCompositeDatatypeDefinition pdef = (RuntimeCompositeDatatypeDefinition) nextChild.getSingleChildOrThrow(); + defn.getDefinition().addType().setCode(DataTypeEnum.VALUESET_BINDER.fromCodeString(pdef.getName())); + } else { + RuntimeResourceBlockDefinition pdef = (RuntimeResourceBlockDefinition) nextChild.getSingleChildOrThrow(); + scanForExtensions(theProfile, pdef, theExtensionDefToCode); + + for (RuntimeChildDeclaredExtensionDefinition nextChildExt : pdef.getExtensions()) { + StructureElementDefinitionType type = defn.getDefinition().addType(); + type.setCode(DataTypeEnum.EXTENSION); + type.setProfile("#" + theExtensionDefToCode.get(nextChildExt)); + } + + } + } + + return theExtensionDefToCode; + } + + @Override + public IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer) { + return new ServerProfileProvider(theRestfulServer.getFhirContext()); + } + + @Override + public FhirVersionEnum getVersion() { + return FhirVersionEnum.DSTU1; + } + + @Override + public InputStream getFhirVersionPropertiesFile() { + InputStream str = FhirDstu1.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/fhirversion.properties"); + if (str == null) { + str = FhirDstu1.class.getResourceAsStream("ca/uhn/fhir/model/dstu/fhirversion.properties"); + } + if (str == null) { + throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties"); + } + return str; + } + + + + +} diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirContextTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirContextTest.java index cd5f1174cd6..338121939e9 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirContextTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirContextTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.context; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import org.junit.Test; @@ -23,4 +24,14 @@ public class FhirContextTest { assertEquals("Binary", def.getName()); } + @Test + public void testUnknownVersion() { + try { + new FhirContext(FhirVersionEnum.DEV); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("Could not find the HAPI-FHIR structure JAR on the classpath for version {0}")); + } + } + } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirVersionEnumTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirVersionEnumTest.java new file mode 100644 index 00000000000..47d1b0fe9fa --- /dev/null +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/context/FhirVersionEnumTest.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.context; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class FhirVersionEnumTest { + + @Test + public void testIsNewerThan() { + assertTrue(FhirVersionEnum.DEV.isNewerThan(FhirVersionEnum.DSTU1)); + assertFalse(FhirVersionEnum.DSTU1.isNewerThan(FhirVersionEnum.DEV)); + assertFalse(FhirVersionEnum.DEV.isNewerThan(FhirVersionEnum.DEV)); + } + +} diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java index 6786d06f53a..c91d77b13e2 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -18,7 +18,6 @@ import net.sf.json.JSON; import net.sf.json.JSONSerializer; import org.apache.commons.io.IOUtils; -import org.hamcrest.Matchers; import org.hamcrest.core.IsNot; import org.hamcrest.core.StringContains; import org.hamcrest.text.StringContainsInOrder; diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java index 594d55ba10d..c8be13f7ddb 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java @@ -14,6 +14,7 @@ import ca.uhn.fhir.model.api.BaseResource; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.base.resource.ResourceMetadataMap; import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.resource.Patient; @@ -180,7 +181,7 @@ public class ServerInvalidDefinitionTest { } @Override - public void setResourceMetadata(Map, Object> theMap) { + public void setResourceMetadata(ResourceMetadataMap theMap) { } @Override @@ -202,7 +203,7 @@ public class ServerInvalidDefinitionTest { } @Override - public Map, Object> getResourceMetadata() { + public ResourceMetadataMap getResourceMetadata() { return null; } diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js index 3b191735de8..035251568eb 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js +++ b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js @@ -132,7 +132,7 @@ function addSearchControls(theConformance, theSearchParamType, theSearchParamNam } else if (theSearchParamType == 'date') { addSearchControlDate(theSearchParamName, theContainerRowNum, theRowNum, true); addSearchControlDate(theSearchParamName, theContainerRowNum, theRowNum, false); - } else if (theSearchParamType == 'reference' && theSearchParamChain.length == 0) { + } else if (theSearchParamType == 'reference' && (!theSearchParamChain || theSearchParamChain.length == 0)) { /* * This is a reference parameter with no chain options, so just display a simple * text box for the ID of the referenced resource diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java index 085b0f926d9..7bfe2cceed7 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.model.dstu; +import java.io.InputStream; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IResource; @@ -23,4 +27,20 @@ public class FhirDstu1 implements IFhirVersion { throw new UnsupportedOperationException(); } + @Override + public InputStream getFhirVersionPropertiesFile() { + InputStream str = FhirDstu1.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/fhirversion.properties"); + if (str == null) { + str = FhirDstu1.class.getResourceAsStream("ca/uhn/fhir/model/dstu/fhirversion.properties"); + } + if (str == null) { + throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties"); + } + return str; + } + + @Override + public FhirVersionEnum getVersion() { + return FhirVersionEnum.DSTU1; + } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index bde50cc75dd..6d753c6778a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -30,6 +30,18 @@ ]]> + + API Change: The IResource#getResourceMetadata() method has been changed + from returning + Map<ResourceMetadataKeyEnum<?>, Object> + to returning a new type called + ResourceMetadataMap. This new type implements + Map<ResourceMetadataKeyEnum<?>, Object> + itself, so this change should not break existing code, but may + require a clean build in order to run correctly. + ]]> + Profile generation on the server was not working due to IdDt being incorrectly used. Thanks to Bill de Beaubien for the pull request!