From 39dd48bc415d09d7b53fc5b1ed162c9d099a04f3 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 17 Jun 2015 14:32:11 -0400 Subject: [PATCH] Automatically populate Bundle.entry.base when encoding a bunde --- .../context/BaseRuntimeChildDefinition.java | 2 + .../BaseRuntimeDeclaredChildDefinition.java | 34 ++++++ ...imeChildUndeclaredExtensionDefinition.java | 11 ++ .../ca/uhn/fhir/model/primitive/IdDt.java | 9 ++ .../java/ca/uhn/fhir/parser/BaseParser.java | 84 ++++++++++++-- .../java/ca/uhn/fhir/parser/JsonParser.java | 6 +- .../java/ca/uhn/fhir/parser/XmlParser.java | 102 +++++++++-------- .../uhn/fhir/rest/api/ValidationModeEnum.java | 2 +- .../uhn/fhir/rest/server/RestfulServer.java | 2 +- .../java/ca/uhn/fhir/util/FhirTerser.java | 44 ++++++-- .../jpa/dao/FhirBundleResourceDaoDstu2.java | 20 ++++ .../uhn/fhir/parser/XmlParserDstu2Test.java | 104 +++++++++++++++++- .../org/hl7/fhir/instance/model/IdType.java | 10 ++ src/changes/changes.xml | 5 +- src/site/resources/hapi.css | 15 ++- src/site/xdoc/download.xml.vm | 43 ++++---- 16 files changed, 386 insertions(+), 107 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java index 5685f138850..de200b7d5ff 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java @@ -58,6 +58,8 @@ public abstract class BaseRuntimeChildDefinition { public abstract int getMin(); public interface IMutator { + void setValue(Object theTarget, IBase theValue); + void addValue(Object theTarget, IBase theValue); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java index d1431fa8148..ae1462a6614 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java @@ -194,6 +194,11 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil throw new ConfigurationException("Failed to set value", e); } } + + @Override + public void setValue(Object theTarget, IBase theValue) { + addValue(theTarget, theValue); + } } private final class FieldPlainAccessor implements IAccessor { @@ -217,6 +222,15 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil protected final class FieldListMutator implements IMutator { @Override public void addValue(Object theTarget, IBase theValue) { + addValue(theTarget, theValue, false); + } + + @Override + public void setValue(Object theTarget, IBase theValue) { + addValue(theTarget, theValue, true); + } + + private void addValue(Object theTarget, IBase theValue, boolean theClear) { try { @SuppressWarnings("unchecked") List existingList = (List) myField.get(theTarget); @@ -224,6 +238,9 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil existingList = new ArrayList(2); myField.set(theTarget, existingList); } + if (theClear) { + existingList.clear(); + } existingList.add(theValue); } catch (IllegalArgumentException e) { throw new ConfigurationException("Failed to set value", e); @@ -283,6 +300,10 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil @Override public void addValue(Object theTarget, IBase theValue) { + addValue(theTarget, false, theValue); + } + + private void addValue(Object theTarget, boolean theClear, IBase theValue) { List existingList = myAccessor.getValues(theTarget); if (existingList == null) { existingList = new ArrayList(); @@ -296,8 +317,16 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil throw new ConfigurationException("Failed to get value", e); } } + if (theClear) { + existingList.clear(); + } existingList.add(theValue); } + + @Override + public void setValue(Object theTarget, IBase theValue) { + addValue(theTarget, true, theValue); + } } private final class PlainAccessor implements IAccessor { @@ -348,6 +377,11 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil throw new ConfigurationException("Failed to get value", e); } } + + @Override + public void setValue(Object theTarget, IBase theValue) { + addValue(theTarget, theValue); + } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java index 50d1f5b192e..db6c1dcee92 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildUndeclaredExtensionDefinition.java @@ -104,6 +104,17 @@ public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildD target.getUndeclaredExtensions().add((ExtensionDt) theValue); } } + + @Override + public void setValue(Object theTarget, IBase theValue) { + ExtensionDt target = (ExtensionDt) theTarget; + if (theValue instanceof IDatatype) { + target.setValue((IDatatype) theTarget); + } else { + target.getUndeclaredExtensions().clear(); + target.getUndeclaredExtensions().add((ExtensionDt) theValue); + } + } }; } 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 5bc0efa6448..2618bce9c7d 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 @@ -23,6 +23,7 @@ package ca.uhn.fhir.model.primitive; import static org.apache.commons.lang3.StringUtils.*; import java.math.BigDecimal; +import java.util.UUID; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -634,4 +635,12 @@ public class IdDt extends UriDt implements IPrimitiveDatatype, IIdType { return theIdPart.toString(); } + /** + * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new, randomly + * created UUID generated by {@link UUID#randomUUID()} + */ + public static IdDt newRandomUuid() { + return new IdDt("urn:uuid:" + UUID.randomUUID().toString()); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 53c993899fe..14d6a9b68e0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -39,6 +39,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IAnyResource; @@ -61,6 +62,8 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.util.FhirTerser; +import ca.uhn.fhir.util.ObjectUtil; public abstract class BaseParser implements IParser { @@ -74,7 +77,8 @@ public abstract class BaseParser implements IParser { /** * Constructor - * @param theParserErrorHandler + * + * @param theParserErrorHandler */ public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { myContext = theContext; @@ -205,6 +209,10 @@ public abstract class BaseParser implements IParser { return resourceBaseUrl; } + protected abstract void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException; + + protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; + protected abstract T doParseResource(Class theResourceType, Reader theReader) throws DataFormatException; @Override @@ -222,6 +230,13 @@ public abstract class BaseParser implements IParser { return stringWriter.toString(); } + @Override + public final void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException { + Validate.notNull(theBundle, "theBundle must not be null"); + Validate.notNull(theWriter, "theWriter must not be null"); + doEncodeBundleToWriter(theBundle, theWriter); + } + @Override public String encodeResourceToString(IBaseResource theResource) throws DataFormatException { Writer stringWriter = new StringWriter(); @@ -233,6 +248,18 @@ public abstract class BaseParser implements IParser { return stringWriter.toString(); } + @Override + public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { + Validate.notNull(theResource, "theResource can not be null"); + Validate.notNull(theWriter, "theWriter can not be null"); + + if (theResource instanceof IBaseBundle) { + fixBaseLinksForBundle((IBaseBundle) theResource); + } + + doEncodeResourceToWriter(theResource, theWriter); + } + @Override public String encodeTagListToString(TagList theTagList) { Writer stringWriter = new StringWriter(); @@ -244,6 +271,49 @@ public abstract class BaseParser implements IParser { return stringWriter.toString(); } + + /** + * If individual resources in the bundle have an ID that has the base set, we make sure that Bundle.entry.base gets set as needed. + */ + private void fixBaseLinksForBundle(IBaseBundle theBundle) { + /* + * ATTENTION IF YOU ARE EDITING THIS: + * There are two versions of this method, one for DSTU1/atom bundle and + * one for DSTU2/resource bundle. If you edit one, edit both and also + * update unit tests for both. + */ + FhirTerser t = myContext.newTerser(); + IPrimitiveType element = t.getSingleValueOrNull(theBundle, "base", IPrimitiveType.class); + String bundleBase = element != null ? element.getValueAsString() : null; + + for (IBase nextEntry : t.getValues(theBundle, "Bundle.entry", IBase.class)) { + IBaseResource resource = t.getSingleValueOrNull(nextEntry, "resource", IBaseResource.class); + if (resource == null) { + continue; + } + + IPrimitiveType baseElement = t.getSingleValueOrNull(nextEntry, "base", IPrimitiveType.class); + String entryBase = baseElement != null ? baseElement.getValueAsString() : null; + if (isNotBlank(entryBase)) { + continue; + } + + IIdType resourceId = resource.getIdElement(); + String resourceIdBase = resourceId.getBaseUrl(); + if (isNotBlank(resourceIdBase)) { + if (!ObjectUtil.equals(bundleBase, resourceIdBase)) { + if (baseElement == null) { + baseElement = (IPrimitiveType) myContext.getElementDefinition("uri").newInstance(); + BaseRuntimeElementCompositeDefinition entryDef = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(nextEntry.getClass()); + entryDef.getChildByNameOrThrowDataFormatException("base").getMutator().setValue(nextEntry, baseElement); + } + + baseElement.setValueAsString(resourceIdBase); + } + } + } + } + protected String fixContainedResourceId(String theValue) { if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') { return theValue.substring(1); @@ -330,7 +400,7 @@ public abstract class BaseParser implements IParser { String baseUrl = baseType.getValueAsString(); String idPart = res.getIdElement().getIdPart(); - + String resourceName = resDef.getName(); if (!baseUrl.startsWith("cid:") && !baseUrl.startsWith("urn:")) { res.setId(new IdDt(baseUrl, resourceName, idPart, versionIdPart)); @@ -376,15 +446,15 @@ public abstract class BaseParser implements IParser { } @Override - public BaseParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { - Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); - myErrorHandler = theErrorHandler; + public BaseParser setOmitResourceId(boolean theOmitResourceId) { + myOmitResourceId = theOmitResourceId; return this; } @Override - public BaseParser setOmitResourceId(boolean theOmitResourceId) { - myOmitResourceId = theOmitResourceId; + public BaseParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { + Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); + myErrorHandler = theErrorHandler; return this; } 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 fcfdad2b0c8..9bf574db67c 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 @@ -180,7 +180,7 @@ public class JsonParser extends BaseParser implements IParser { } @Override - public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException { + public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException { JsonGenerator eventWriter = createJsonGenerator(theWriter); if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { encodeBundleToWriterInDstu2Format(theBundle, eventWriter); @@ -777,9 +777,7 @@ public class JsonParser extends BaseParser implements IParser { } @Override - public void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException { - Validate.notNull(theResource, "Resource can not be null"); - + protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException { JsonGenerator eventWriter = createJsonGenerator(theWriter); RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); 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 e2be433ef03..37972bb1a0a 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 @@ -84,14 +84,14 @@ import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper; import ca.uhn.fhir.util.PrettyPrintWriterWrapper; import ca.uhn.fhir.util.XmlUtil; /** - * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use - * {@link FhirContext#newXmlParser()} to get an instance. + * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use {@link FhirContext#newXmlParser()} to get an instance. */ public class XmlParser extends BaseParser implements IParser { @@ -110,9 +110,9 @@ public class XmlParser extends BaseParser implements IParser { 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()}. - * @param theParserErrorHandler + * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke {@link FhirContext#newXmlParser()}. + * + * @param theParserErrorHandler */ public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { super(theContext, theParserErrorHandler); @@ -224,13 +224,17 @@ public class XmlParser extends BaseParser implements IParser { @Override public String encodeBundleToString(Bundle theBundle) throws DataFormatException { StringWriter stringWriter = new StringWriter(); - encodeBundleToWriter(theBundle, stringWriter); + try { + encodeBundleToWriter(theBundle, stringWriter); + } catch (IOException e) { + throw new InternalErrorException("IOException writing to StringWriter - Should not happen", e); + } return stringWriter.toString(); } @Override - public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { + public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { try { XMLStreamWriter eventWriter = createXmlWriter(theWriter); if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { @@ -442,7 +446,8 @@ public class XmlParser extends BaseParser implements IParser { theEventWriter.close(); } - private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase nextValue, String childName, BaseRuntimeElementDefinition childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase nextValue, String childName, BaseRuntimeElementDefinition childDef, + String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException { if (nextValue == null || nextValue.isEmpty()) { if (isChildContained(childDef, theIncludedResource)) { // We still want to go in.. @@ -497,10 +502,9 @@ public class XmlParser extends BaseParser implements IParser { case CONTAINED_RESOURCE_LIST: case CONTAINED_RESOURCES: { /* - * Disable per #103 for (IResource next : value.getContainedResources()) { if - * (getContainedResources().getResourceId(next) != null) { continue; } - * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, - * true, fixContainedResourceId(next.getId().getValue())); theEventWriter.writeEndElement(); } + * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; } + * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue())); + * theEventWriter.writeEndElement(); } */ for (IBaseResource next : getContainedResources().getContainedResources()) { IIdType resourceId = getContainedResources().getResourceId(next); @@ -544,7 +548,8 @@ public class XmlParser extends BaseParser implements IParser { } - private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List children, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List children, + boolean theIncludedResource) throws XMLStreamException, DataFormatException { for (BaseRuntimeChildDefinition nextChild : children) { if (nextChild.getElementName().equals("extension") || nextChild.getElementName().equals("modifierExtension")) { continue; @@ -570,30 +575,30 @@ public class XmlParser extends BaseParser implements IParser { } } else { // Narrative generation not currently supported for HL7org structures -// INarrative narr1 = ((IDomainResource) theResource).getText(); -// BaseNarrativeDt narr2 = null; -// if (gen != null && narr1.isEmpty()) { -// // TODO: need to implement this -// String resourceProfile = myContext.getResourceDefinition(theResource).getResourceProfile(); -// gen.generateNarrative(resourceProfile, theResource, null); -// } -// if (narr2 != null) { -// RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; -// String childName = nextChild.getChildNameByDatatype(child.getDatatype()); -// BaseRuntimeElementDefinition type = child.getChildByName(childName); -// encodeChildElementToStreamWriter(theResource, theEventWriter, narr2, childName, type, null, theIncludedResource); -// continue; -// } + // INarrative narr1 = ((IDomainResource) theResource).getText(); + // BaseNarrativeDt narr2 = null; + // if (gen != null && narr1.isEmpty()) { + // // TODO: need to implement this + // String resourceProfile = myContext.getResourceDefinition(theResource).getResourceProfile(); + // gen.generateNarrative(resourceProfile, theResource, null); + // } + // if (narr2 != null) { + // RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; + // String childName = nextChild.getChildNameByDatatype(child.getDatatype()); + // BaseRuntimeElementDefinition type = child.getChildByName(childName); + // encodeChildElementToStreamWriter(theResource, theEventWriter, narr2, childName, type, null, theIncludedResource); + // continue; + // } } } - if (nextChild instanceof RuntimeChildContainedResources) { if (!theIncludedResource) { - encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theIncludedResource); + encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, + theIncludedResource); } } else { - + List values = nextChild.getAccessor().getValues(theElement); if (values == null || values.isEmpty()) { continue; @@ -636,7 +641,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, + boolean theIncludedResource) throws XMLStreamException, DataFormatException { encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getExtensions(), theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getChildren(), theIncludedResource); @@ -659,9 +665,8 @@ public class XmlParser extends BaseParser implements IParser { } /** - * This is just to work around the fact that casting java.util.List to - * java.util.List> seems to be rejected by the compiler - * some of the time. + * This is just to work around the fact that casting java.util.List to java.util.List> seems to be + * rejected by the compiler some of the time. */ private > List> toBaseExtensionList(final List theList) { List> retVal = new ArrayList>(theList.size()); @@ -673,7 +678,7 @@ public class XmlParser extends BaseParser implements IParser { String reference = determineReferenceText(theRef); encodeExtensionsIfPresent(theResource, theEventWriter, theRef, theIncludedResource); - + if (StringUtils.isNotBlank(reference)) { theEventWriter.writeStartElement(RESREF_REFERENCE); theEventWriter.writeAttribute("value", reference); @@ -686,10 +691,11 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, + BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { /* - * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy - * way to make that happen, but hopefully this won't matter as much once we use the HL7 structures + * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy way to make that happen, but hopefully this won't matter as much once we use the + * HL7 structures */ List preExtensionChildren = new ArrayList(); @@ -714,18 +720,7 @@ public class XmlParser extends BaseParser implements IParser { } @Override - public String encodeResourceToString(IBaseResource theResource) throws DataFormatException { - if (theResource == null) { - throw new NullPointerException("Resource can not be null"); - } - - Writer stringWriter = new StringWriter(); - encodeResourceToWriter(theResource, stringWriter); - return stringWriter.toString(); - } - - @Override - public void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException { + public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException { XMLStreamWriter eventWriter; try { eventWriter = createXmlWriter(theWriter); @@ -756,7 +751,7 @@ public class XmlParser extends BaseParser implements IParser { if (isOmitResourceId() && !theIncludedResource) { resourceId = null; } - + encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId); } @@ -774,7 +769,7 @@ public class XmlParser extends BaseParser implements IParser { theEventWriter.writeDefaultNamespace(FHIR_NS); if (theResource instanceof IAnyResource) { - + // HL7.org Structures writeOptionalTagWithValue(theEventWriter, "id", theResourceId); encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, resDef, theContainedResource); @@ -890,7 +885,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) + throws XMLStreamException, DataFormatException { for (IBaseExtension next : theExtensions) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { continue; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/ValidationModeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/ValidationModeEnum.java index 4ec0cd9721b..886a00dc78d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/ValidationModeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/ValidationModeEnum.java @@ -45,4 +45,4 @@ public enum ValidationModeEnum implements IBase { public boolean isEmpty() { return false; } -} \ No newline at end of file +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index b938514ae90..a6af0ccfadf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -756,7 +756,7 @@ public class RestfulServer extends HttpServlet { public final void init() throws ServletException { initialize(); try { - ourLog.info("Initializing HAPI FHIR restful server"); + ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode"); ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext()); providedResourceScanner.scanForProvidedResources(this); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index 1b69e6ed40f..37ec4c76780 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -118,7 +118,7 @@ public class FhirTerser { return retVal; } - public List getAllResourceReferences(final IBaseResource theResource) { + public List getAllResourceReferences(final IBaseResource theResource) { final ArrayList retVal = new ArrayList(); BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); visit(theResource, null, null, def, new IModelVisitor() { @@ -168,33 +168,44 @@ public class FhirTerser { } - private List getValues(BaseRuntimeElementCompositeDefinition theCurrentDef, Object theCurrentObj, List theSubList) { + @SuppressWarnings("unchecked") + private List getValues(BaseRuntimeElementCompositeDefinition theCurrentDef, Object theCurrentObj, List theSubList, Class theWantedClass) { String name = theSubList.get(0); BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name); List values = nextDef.getAccessor().getValues(theCurrentObj); - List retVal = new ArrayList(); + List retVal = new ArrayList(); if (theSubList.size() == 1) { if (nextDef instanceof RuntimeChildChoiceDefinition) { for (IBase next : values) { if (next != null) { if (name.endsWith("[x]")) { - retVal.add(next); + if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { + retVal.add((T) next); + } } else { String childName = nextDef.getChildNameByDatatype(next.getClass()); if (theSubList.get(0).equals(childName)) { - retVal.add(next); + if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { + retVal.add((T) next); + } } } } } } else { - retVal.addAll(values); + for (IBase next : values) { + if (next != null) { + if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) { + retVal.add((T) next); + } + } + } } } else { for (IBase nextElement : values) { BaseRuntimeElementCompositeDefinition nextChildDef = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(nextElement.getClass()); - List foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size())); + List foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass); retVal.addAll(foundValues); } } @@ -202,6 +213,13 @@ public class FhirTerser { } public List getValues(IBaseResource theResource, String thePath) { + Class wantedClass = Object.class; + + return getValues(theResource, thePath, wantedClass); + + } + + public List getValues(IBaseResource theResource, String thePath, Class theWantedClass) { RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); BaseRuntimeElementCompositeDefinition currentDef = def; @@ -212,8 +230,7 @@ public class FhirTerser { if (subList.size() < 1) { throw new ConfigurationException("Invalid path: " + thePath); } - return getValues(currentDef, currentObj, subList); - + return getValues(currentDef, currentObj, subList, theWantedClass); } private List addNameToList(List theCurrentList, BaseRuntimeChildDefinition theChildDefinition) { @@ -469,6 +486,12 @@ public class FhirTerser { } public Object getSingleValueOrNull(IBase theTarget, String thePath) { + Class wantedType = Object.class; + + return getSingleValueOrNull(theTarget, thePath, wantedType); + } + + public T getSingleValueOrNull(IBase theTarget, String thePath, Class theWantedType) { Validate.notNull(theTarget, "theTarget must not be null"); Validate.notBlank(thePath, "thePath must not be empty"); @@ -481,7 +504,7 @@ public class FhirTerser { Object currentObj = theTarget; List parts = Arrays.asList(thePath.split("\\.")); - List retVal = getValues(currentDef, currentObj, parts); + List retVal = getValues(currentDef, currentObj, parts, theWantedType); if (retVal.isEmpty()) { return null; } else { @@ -489,4 +512,5 @@ public class FhirTerser { } } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java index 238b4fac512..72663d4796c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirBundleResourceDaoDstu2.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.dao; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; import ca.uhn.fhir.model.primitive.UriDt; diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java index 7af67ce9fb5..b66be01abb8 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java @@ -127,7 +127,8 @@ public class XmlParserDstu2Test { assertThat(str, not(containsString("meta"))); assertThat(str, containsString("")); } - + + @Test public void testEncodeAndParseBundleWithoutResourceIds() { Organization org = new Organization(); @@ -211,6 +212,7 @@ public class XmlParserDstu2Test { } + @Test public void testEncodeAndParseExtensionOnResourceReference() { DataElement de = new DataElement(); @@ -236,6 +238,7 @@ public class XmlParserDstu2Test { } + @Test public void testEncodeAndParseExtensions() throws Exception { @@ -381,6 +384,7 @@ public class XmlParserDstu2Test { assertEquals(new Tag("scheme1", "term1", "label1"), tagList.get(0)); assertEquals(new Tag("scheme2", "term2", "label2"), tagList.get(1)); } + @Test public void testEncodeAndParseMetaProfiles() { @@ -427,7 +431,7 @@ public class XmlParserDstu2Test { assertEquals(new Tag("scheme1", "term1", "label1"), tagList.get(0)); assertEquals(new Tag("scheme2", "term2", "label2"), tagList.get(1)); } - + @Test public void testEncodeAndParseSecurityLabels() { Patient p = new Patient(); @@ -485,7 +489,7 @@ public class XmlParserDstu2Test { assertEquals(false, label.getPrimary()); assertEquals("VERSION2", label.getVersion()); } - + /** * See #103 */ @@ -510,7 +514,7 @@ public class XmlParserDstu2Test { parsed = parser.parseResource(Composition.class, string); assertEquals(2, parsed.getContained().getContainedResources().size()); } - + /** * See #103 */ @@ -536,7 +540,6 @@ public class XmlParserDstu2Test { assertEquals(2, parsed.getContained().getContainedResources().size()); } - @Test public void testEncodeBinaryWithNoContentType() { Binary b = new Binary(); @@ -548,6 +551,97 @@ public class XmlParserDstu2Test { assertEquals("", output); } + @Test + public void testEncodeBundleContainingResourceWithUuidBase() { + Patient p = new Patient(); + p.setId(IdDt.newRandomUuid()); + p.addName().addFamily("PATIENT"); + + ca.uhn.fhir.model.dstu2.resource.Bundle b = new ca.uhn.fhir.model.dstu2.resource.Bundle(); + b.addEntry().setResource(p); + + String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(b); + ourLog.info(encoded); + assertThat(encoded, stringContainsInOrder("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", " When parsing Bundles, if Bundle.entry.base is set to "cid:" (for DSTU1) or "urn:uuid:" / "urn:oid:" (for DSTU2) this is now correctly passed as - the base in resource.getId() + the base in resource.getId(). Conversely, when + encoding bundles, if a resource ID has a base defined, + and Bundle.entry.base is empty, it will now be + automatically set by the parser. Add fluent client method for validate operation, and support the diff --git a/src/site/resources/hapi.css b/src/site/resources/hapi.css index dbd56346e4a..bba7409daff 100644 --- a/src/site/resources/hapi.css +++ b/src/site/resources/hapi.css @@ -113,15 +113,20 @@ a[name]:before { margin: auto; } +DIV.main-body DIV.row DIV.span8 DIV.body-content { + top: -8px; + position: relative; +} .section h2 { - border-bottom-width: 2px; - border-bottom-style: solid; - border-bottom-color: #CF4711; - background-color: #FF5721; + border-bottom: 2px solid #CF4711; + background-color: #FF7741; color: #FFA; font-size: 1.5em; line-height: 1.4em; + border-radius: 6px; + padding-left: 5px; + padding-right: 5px; } .section h3 { @@ -195,4 +200,4 @@ a,a.externalLink,a:active,a:hover,a:link,a:visited { DIV.sidebar-nav UL LI UL LI { font-size: 0.9em; } -*/ \ No newline at end of file +*/ diff --git a/src/site/xdoc/download.xml.vm b/src/site/xdoc/download.xml.vm index 95fd243057d..22d41944815 100644 --- a/src/site/xdoc/download.xml.vm +++ b/src/site/xdoc/download.xml.vm @@ -21,6 +21,9 @@ GitHub Release Section.

+ + +

To use HAPI in your application, at a minimum you need to include the HAPI-FHIR core JAR hapi-fhir-base-[version].jar, as well as at least one "structures" JAR. @@ -49,7 +52,7 @@ ${hapi_stable_version} ]]> - +

HAPI also has a hapi-fhir-structures-dstu2-[version].jar, which @@ -72,24 +75,6 @@ ]]> - - -

- If you are using Gradle, you may use the following dependencies. Note that if - you are doing Android development, you may want to use our - Android build instead. -

-

- DSTU1: -

- -

- DSTU2: -

- - @@ -105,7 +90,25 @@ compile 'ca.uhn.hapi.fhir:hapi-fhir-structures-dstu2:${hapi_stable_version}']]>< ]]> - + +
+ +
+

+ If you are using Gradle, you may use the following dependencies. Note that if + you are doing Android development, you may want to use our + Android build instead. +

+

+ DSTU1: +

+ +

+ DSTU2: +

+