Enhanced elements (#1192)

Squashed merge: Add elements exclude mode

* Start working on elements enhancement

* Work on elements projection

* Work on elements filter

* Feature is now working

* Just some cleanup

* Address compile issues
This commit is contained in:
James Agnew 2019-02-03 16:41:33 -05:00 committed by GitHub
parent 447c394cac
commit e401ec86e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1582 additions and 1179 deletions

View File

@ -30,11 +30,13 @@ import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import java.io.*; import java.io.*;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -47,11 +49,9 @@ public abstract class BaseParser implements IParser {
private ContainedResources myContainedResources; private ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly; private boolean myEncodeElementsAppliesToChildResourcesOnly;
private FhirContext myContext; private FhirContext myContext;
private Set<String> myDontEncodeElements; private List<ElementsPath> myDontEncodeElements;
private boolean myDontEncodeElementsIncludesStars; private List<ElementsPath> myEncodeElements;
private Set<String> myEncodeElements;
private Set<String> myEncodeElementsAppliesToResourceTypes; private Set<String> myEncodeElementsAppliesToResourceTypes;
private boolean myEncodeElementsIncludesStars;
private IIdType myEncodeForceResourceId; private IIdType myEncodeForceResourceId;
private IParserErrorHandler myErrorHandler; private IParserErrorHandler myErrorHandler;
private boolean myOmitResourceId; private boolean myOmitResourceId;
@ -65,20 +65,19 @@ public abstract class BaseParser implements IParser {
/** /**
* Constructor * Constructor
*
* @param theParserErrorHandler
*/ */
public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
myContext = theContext; myContext = theContext;
myErrorHandler = theParserErrorHandler; myErrorHandler = theParserErrorHandler;
} }
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) { protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass()); BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension(); final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
return new Iterable<BaseParser.CompositeChildElement>() { return new Iterable<BaseParser.CompositeChildElement>() {
@Override @Override
public Iterator<CompositeChildElement> iterator() { public Iterator<CompositeChildElement> iterator() {
@ -106,7 +105,7 @@ public abstract class BaseParser implements IParser {
return false; return false;
} }
myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource); myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theEncodeContext);
/* /*
* There are lots of reasons we might skip encoding a particular child * There are lots of reasons we might skip encoding a particular child
@ -181,8 +180,6 @@ public abstract class BaseParser implements IParser {
theContained.getExistingIdToContainedResource().put(nextId, next); theContained.getExistingIdToContainedResource().put(nextId, next);
} }
} }
} else {
// no resources to contain
} }
List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
@ -279,7 +276,7 @@ public abstract class BaseParser implements IParser {
return ref.getValue(); return ref.getValue();
} }
protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException;
protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException; protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
@ -296,15 +293,27 @@ public abstract class BaseParser implements IParser {
@Override @Override
public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
EncodeContext encodeContext = new EncodeContext();
encodeResourceToWriter(theResource, theWriter, encodeContext);
}
protected void encodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
Validate.notNull(theResource, "theResource can not be null"); Validate.notNull(theResource, "theResource can not be null");
Validate.notNull(theWriter, "theWriter can not be null"); Validate.notNull(theWriter, "theWriter can not be null");
Validate.notNull(theEncodeContext, "theEncodeContext can not be null");
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
} }
doEncodeResourceToWriter(theResource, theWriter); String resourceName = myContext.getResourceDefinition(theResource).getName();
theEncodeContext.pushPath(resourceName, true);
doEncodeResourceToWriter(theResource, theWriter, theEncodeContext);
theEncodeContext.popPath();
} }
private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) { private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) {
@ -392,43 +401,32 @@ public abstract class BaseParser implements IParser {
return myDontStripVersionsFromReferencesAtPaths; return myDontStripVersionsFromReferencesAtPaths;
} }
/**
* See {@link #setEncodeElements(Set)}
*/
@Override
public Set<String> getEncodeElements() {
return myEncodeElements;
}
@Override @Override
public void setEncodeElements(Set<String> theEncodeElements) { public void setEncodeElements(Set<String> theEncodeElements) {
myEncodeElementsIncludesStars = false;
if (theEncodeElements == null || theEncodeElements.isEmpty()) { if (theEncodeElements == null || theEncodeElements.isEmpty()) {
myEncodeElements = null; myEncodeElements = null;
} else {
myEncodeElements = theEncodeElements;
for (String next : theEncodeElements) {
if (next.startsWith("*.")) {
myEncodeElementsIncludesStars = true;
}
}
}
}
/**
* See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
*/
@Override
public Set<String> getEncodeElementsAppliesToResourceTypes() {
return myEncodeElementsAppliesToResourceTypes;
}
@Override
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
myEncodeElementsAppliesToResourceTypes = null; myEncodeElementsAppliesToResourceTypes = null;
} else { } else {
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes; myEncodeElements = theEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
myEncodeElementsAppliesToResourceTypes = new HashSet<>();
for (String next : myEncodeElements.stream().map(t -> t.getPath().get(0).getName()).collect(Collectors.toList())) {
if (next.startsWith("*")) {
myEncodeElementsAppliesToResourceTypes = null;
break;
}
int dotIdx = next.indexOf('.');
if (dotIdx == -1) {
myEncodeElementsAppliesToResourceTypes.add(next);
} else {
myEncodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx));
}
}
} }
} }
@ -448,7 +446,7 @@ public abstract class BaseParser implements IParser {
} }
protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) { protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) {
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<Map.Entry<ResourceMetadataKeyEnum<?>, Object>>(); List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<>();
for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) { for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) {
if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) { if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) {
extensionMetadataKeys.add(entry); extensionMetadataKeys.add(entry);
@ -466,9 +464,9 @@ public abstract class BaseParser implements IParser {
return url; return url;
} }
protected TagList getMetaTagsForEncoding(IResource theIResource) { protected TagList getMetaTagsForEncoding(IResource theIResource, EncodeContext theEncodeContext) {
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource); TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
if (shouldAddSubsettedTag()) { if (shouldAddSubsettedTag(theEncodeContext)) {
tags = new TagList(tags); tags = new TagList(tags);
tags.add(new Tag(getSubsettedCodeSystem(), Constants.TAG_SUBSETTED_CODE, subsetDescription())); tags.add(new Tag(getSubsettedCodeSystem(), Constants.TAG_SUBSETTED_CODE, subsetDescription()));
} }
@ -709,7 +707,7 @@ public abstract class BaseParser implements IParser {
} }
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues, protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
CompositeChildElement theCompositeChildElement) { CompositeChildElement theCompositeChildElement, EncodeContext theEncodeContext) {
if (myContext.getVersion().getVersion().isRi()) { if (myContext.getVersion().getVersion().isRi()) {
/* /*
@ -754,7 +752,7 @@ public abstract class BaseParser implements IParser {
} }
} }
if (shouldAddSubsettedTag()) { if (shouldAddSubsettedTag(theEncodeContext)) {
IBaseCoding coding = metaValue.addTag(); IBaseCoding coding = metaValue.addTag();
coding.setCode(Constants.TAG_SUBSETTED_CODE); coding.setCode(Constants.TAG_SUBSETTED_CODE);
coding.setSystem(getSubsettedCodeSystem()); coding.setSystem(getSubsettedCodeSystem());
@ -806,16 +804,13 @@ public abstract class BaseParser implements IParser {
@Override @Override
public void setDontEncodeElements(Set<String> theDontEncodeElements) { public void setDontEncodeElements(Set<String> theDontEncodeElements) {
myDontEncodeElementsIncludesStars = false;
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) { if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
myDontEncodeElements = null; myDontEncodeElements = null;
} else { } else {
myDontEncodeElements = theDontEncodeElements; myDontEncodeElements = theDontEncodeElements
for (String next : theDontEncodeElements) { .stream()
if (next.startsWith("*.")) { .map(ElementsPath::new)
myDontEncodeElementsIncludesStars = true; .collect(Collectors.toList());
}
}
} }
} }
@ -885,22 +880,39 @@ public abstract class BaseParser implements IParser {
return this; return this;
} }
protected boolean shouldAddSubsettedTag() { protected boolean shouldAddSubsettedTag(EncodeContext theEncodeContext) {
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null; if (isSummaryMode()) {
return true;
}
if (isSuppressNarratives()) {
return true;
}
if (myEncodeElements != null) {
if (isEncodeElementsAppliesToChildResourcesOnly() && theEncodeContext.getResourcePath().size() < 2) {
return false;
} }
protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) { String currentResourceName = theEncodeContext.getResourcePath().get(theEncodeContext.getResourcePath().size() - 1).getName();
if (myEncodeElementsAppliesToResourceTypes == null || myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
return true;
}
}
return false;
}
protected boolean shouldEncodeResourceId(IBaseResource theResource, EncodeContext theEncodeContext) {
boolean retVal = true; boolean retVal = true;
if (isOmitResourceId()) { if (isOmitResourceId()) {
retVal = false; retVal = false;
} else { } else {
if (myDontEncodeElements != null) { if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName(); String resourceName = myContext.getResourceDefinition(theResource).getName();
if (myDontEncodeElements.contains(resourceName + ".id")) { if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath(resourceName + ".id"))) {
retVal = false; retVal = false;
} else if (myDontEncodeElements.contains("*.id")) { } else if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("*.id"))) {
retVal = false; retVal = false;
} else if (theSubResource == false && myDontEncodeElements.contains("id")) { } else if (theEncodeContext.getResourcePath().size() == 1 && myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("id"))) {
retVal = false; retVal = false;
} }
} }
@ -914,9 +926,11 @@ public abstract class BaseParser implements IParser {
protected boolean shouldEncodeResourceMeta(IResource theResource) { protected boolean shouldEncodeResourceMeta(IResource theResource) {
if (myDontEncodeElements != null) { if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName(); String resourceName = myContext.getResourceDefinition(theResource).getName();
if (myDontEncodeElements.contains(resourceName + ".meta")) { if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath(resourceName + ".meta"))) {
return false; return false;
} else return !myDontEncodeElements.contains("*.meta"); } else {
return myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("*.meta"));
}
} }
return true; return true;
} }
@ -965,13 +979,13 @@ public abstract class BaseParser implements IParser {
private final BaseRuntimeChildDefinition myDef; private final BaseRuntimeChildDefinition myDef;
private final CompositeChildElement myParent; private final CompositeChildElement myParent;
private final RuntimeResourceDefinition myResDef; private final RuntimeResourceDefinition myResDef;
private final boolean mySubResource; private final EncodeContext myEncodeContext;
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) { public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) {
myDef = theDef; myDef = theDef;
myParent = theParent; myParent = theParent;
myResDef = null; myResDef = null;
mySubResource = theSubResource; myEncodeContext = theEncodeContext;
if (ourLog.isTraceEnabled()) { if (ourLog.isTraceEnabled()) {
if (theParent != null) { if (theParent != null) {
@ -986,11 +1000,11 @@ public abstract class BaseParser implements IParser {
} }
public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) { public CompositeChildElement(RuntimeResourceDefinition theResDef, EncodeContext theEncodeContext) {
myResDef = theResDef; myResDef = theResDef;
myDef = null; myDef = null;
myParent = null; myParent = null;
mySubResource = theSubResource; myEncodeContext = theEncodeContext;
} }
private void addParent(CompositeChildElement theParent, StringBuilder theB) { private void addParent(CompositeChildElement theParent, StringBuilder theB) {
@ -1038,71 +1052,75 @@ public abstract class BaseParser implements IParser {
} }
} }
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { private boolean checkIfParentShouldBeEncodedAndBuildPath() {
Set<String> encodeElements = myEncodeElements; List<ElementsPath> encodeElements = myEncodeElements;
if (encodeElements != null && encodeElements.isEmpty() == false) {
if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) { String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName();
if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
encodeElements = null; encodeElements = null;
} }
}
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
}
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true);
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
}
private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) { /*
if (myResDef != null) { * We force the meta tag to be encoded even if it's not specified as an element in the
if (theResourceTypes != null) { * elements filter, specifically because we'll need it in order to automatically add
if (!theResourceTypes.contains(myResDef.getName())) { * the SUBSETTED tag
return true; */
}
}
if (theStarPass) {
thePathBuilder.append('*');
} else {
thePathBuilder.append(myResDef.getName());
}
if (theElements == null) {
return true;
}
return theElements.contains(thePathBuilder.toString());
} else if (myParent != null) {
boolean parentCheck;
if (theCheckingForWhitelist) {
parentCheck = myParent.checkIfParentShouldBeEncodedAndBuildPath(thePathBuilder, theStarPass);
} else {
parentCheck = myParent.checkIfParentShouldNotBeEncodedAndBuildPath(thePathBuilder, theStarPass);
}
if (parentCheck) {
return true;
}
if (myDef != null) {
if (myDef.getMin() > 0) {
if (theElements.contains("*.(mandatory)")) {
return true;
}
}
thePathBuilder.append('.');
thePathBuilder.append(myDef.getElementName());
String currentPath = thePathBuilder.toString();
boolean retVal = theElements.contains(currentPath);
int dotIdx = currentPath.indexOf('.');
if (!retVal) { if (!retVal) {
if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) { if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) && shouldAddSubsettedTag(myEncodeContext)) {
if (!myParent.isSubResource()) { // The next element is a child of the <meta> element
return true; retVal = true;
} } else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) {
// The next element is the <meta> element
retVal = true;
} }
} }
return retVal; return retVal;
} }
private boolean checkIfParentShouldNotBeEncodedAndBuildPath() {
return checkIfPathMatchesForEncoding(myDontEncodeElements, false);
} }
return false; private boolean checkIfPathMatchesForEncoding(List<ElementsPath> theElements, boolean theCheckingForEncodeElements) {
boolean retVal = false;
myEncodeContext.pushPath(myDef.getElementName(), false);
if (theCheckingForEncodeElements && isEncodeElementsAppliesToChildResourcesOnly() && myEncodeContext.getResourcePath().size() < 2) {
retVal = true;
} else if (theElements == null) {
retVal = true;
} else {
EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath();
ourLog.trace("Current resource path: {}", currentResourcePath);
for (ElementsPath next : theElements) {
if (next.startsWith(currentResourcePath)) {
if (theCheckingForEncodeElements || next.getPath().size() == currentResourcePath.getPath().size()) {
retVal = true;
break;
}
}
if (next.getPath().get(next.getPath().size() - 1).getName().equals("(mandatory)")) {
if (myDef.getMin() > 0) {
retVal = true;
break;
}
if (currentResourcePath.getPath().size() > next.getPath().size()) {
retVal = true;
break;
}
}
}
}
myEncodeContext.popPath();
return retVal;
} }
public BaseRuntimeChildDefinition getDef() { public BaseRuntimeChildDefinition getDef() {
@ -1113,38 +1131,189 @@ public abstract class BaseParser implements IParser {
return myParent; return myParent;
} }
public RuntimeResourceDefinition getResDef() {
return myResDef;
}
private boolean isSubResource() {
return mySubResource;
}
public boolean shouldBeEncoded() { public boolean shouldBeEncoded() {
boolean retVal = true; boolean retVal = true;
if (myEncodeElements != null) { if (myEncodeElements != null) {
retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false); retVal = checkIfParentShouldBeEncodedAndBuildPath();
if (retVal == false && myEncodeElementsIncludesStars) {
retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true);
}
} }
if (retVal && myDontEncodeElements != null) { if (retVal && myDontEncodeElements != null) {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), false); retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
if (retVal && myDontEncodeElementsIncludesStars) {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
} }
}
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
return retVal; return retVal;
} }
} }
protected class EncodeContextPath {
private final List<EncodeContextPathElement> myPath;
public EncodeContextPath() {
myPath = new ArrayList<>(10);
}
public EncodeContextPath(List<EncodeContextPathElement> thePath) {
myPath = thePath;
}
@Override
public String toString() {
return myPath.toString();
}
protected List<EncodeContextPathElement> getPath() {
return myPath;
}
public EncodeContextPath getCurrentResourcePath() {
EncodeContextPath retVal = null;
for (int i = myPath.size() - 1; i >= 0; i--) {
if (myPath.get(i).isResource()) {
retVal = new EncodeContextPath(myPath.subList(i, myPath.size()));
break;
}
}
Validate.isTrue(retVal != null);
return retVal;
}
}
protected class ElementsPath extends EncodeContextPath {
protected ElementsPath(String thePath) {
StringTokenizer tok = new StringTokenizer(thePath, ".");
boolean first = true;
while (tok.hasMoreTokens()) {
String next = tok.nextToken();
if (first && next.equals("*")) {
getPath().add(new EncodeContextPathElement("*", true));
} else if (isNotBlank(next)) {
getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0))));
}
first = false;
}
}
public boolean startsWith(EncodeContextPath theCurrentResourcePath) {
for (int i = 0; i < getPath().size(); i++) {
if (theCurrentResourcePath.getPath().size() == i) {
return true;
}
EncodeContextPathElement expected = getPath().get(i);
EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i);
if (!expected.matches(actual)) {
return false;
}
}
return true;
}
public boolean startsWithPath(String thePath) {
return startsWith(new ElementsPath(thePath));
}
}
/**
* EncodeContext is a shared state object that is passed around the
* encode process
*/
protected class EncodeContext extends EncodeContextPath {
private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10);
protected ArrayList<EncodeContextPathElement> getResourcePath() {
return myResourcePath;
}
public String getLeafResourcePathFirstField() {
String retVal = null;
for (int i = getPath().size() - 1; i >= 0; i--) {
if (getPath().get(i).isResource()) {
break;
} else {
retVal = getPath().get(i).getName();
}
}
return retVal;
}
/**
* Add an element at the end of the path
*/
protected void pushPath(String thePathElement, boolean theResource) {
assert isNotBlank(thePathElement);
assert !thePathElement.contains(".");
assert theResource ^ Character.isLowerCase(thePathElement.charAt(0));
EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource);
getPath().add(element);
if (theResource) {
myResourcePath.add(element);
}
}
/**
* Remove the element at the end of the path
*/
public void popPath() {
EncodeContextPathElement removed = getPath().remove(getPath().size() - 1);
if (removed.isResource()) {
myResourcePath.remove(myResourcePath.size() - 1);
}
}
}
protected class EncodeContextPathElement {
private final String myName;
private final boolean myResource;
public EncodeContextPathElement(String theName, boolean theResource) {
Validate.notBlank(theName);
myName = theName;
myResource = theResource;
}
public boolean matches(EncodeContextPathElement theOther) {
if (myResource != theOther.isResource()) {
return false;
}
if (myName.equals(theOther.getName())) {
return true;
}
if (myName.equals("*")) {
return true;
}
return false;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myName)
.append(myResource)
.toHashCode();
}
@Override
public String toString() {
if (myResource) {
return myName + "(res)";
}
return myName;
}
public String getName() {
return myName;
}
public boolean isResource() {
return myResource;
}
}
static class ContainedResources { static class ContainedResources {
private long myNextContainedId = 1; private long myNextContainedId = 1;
@ -1242,7 +1411,6 @@ public abstract class BaseParser implements IParser {
for (IBaseResource nextResource : getResourceList()) { for (IBaseResource nextResource : getResourceList()) {
if (getResourceToIdMap().get(nextResource) != null) { if (getResourceToIdMap().get(nextResource) != null) {
ids.add(getResourceToIdMap().get(nextResource).getValue()); ids.add(getResourceToIdMap().get(nextResource).getValue());
continue;
} }
} }
@ -1274,24 +1442,24 @@ public abstract class BaseParser implements IParser {
return new ArrayList<>(securityLabels); return new ArrayList<>(securityLabels);
} }
static boolean hasExtensions(IBase theElement) { static boolean hasNoExtensions(IBase theElement) {
if (theElement instanceof ISupportsUndeclaredExtensions) { if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) { if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
return true; return false;
} }
} }
if (theElement instanceof IBaseHasExtensions) { if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions res = (IBaseHasExtensions) theElement; IBaseHasExtensions res = (IBaseHasExtensions) theElement;
if (res.hasExtension()) { if (res.hasExtension()) {
return true; return false;
} }
} }
if (theElement instanceof IBaseHasModifierExtensions) { if (theElement instanceof IBaseHasModifierExtensions) {
IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
return res.hasModifierExtension(); return !res.hasModifierExtension();
} }
return false; return true;
} }
} }

View File

@ -51,16 +51,6 @@ public interface IParser {
void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
/**
* See {@link #setEncodeElements(Set)}
*/
Set<String> getEncodeElements();
/**
* See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
*/
Set<String> getEncodeElementsAppliesToResourceTypes();
/** /**
* If not set to null (as is the default) this ID will be used as the ID in any * If not set to null (as is the default) this ID will be used as the ID in any
* resources encoded by this parser * resources encoded by this parser
@ -258,14 +248,6 @@ public interface IParser {
*/ */
boolean isEncodeElementsAppliesToChildResourcesOnly(); boolean isEncodeElementsAppliesToChildResourcesOnly();
/**
* If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any
* resource types not specified here will be encoded completely, with no elements excluded.
*
* @param theEncodeElementsAppliesToResourceTypes
*/
void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes);
/** /**
* When encoding, force this resource ID to be encoded as the resource ID * When encoding, force this resource ID to be encoded as the resource ID
*/ */

View File

@ -140,21 +140,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
return retVal; return retVal;
} }
public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException { public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
if (myPrettyPrint) { if (myPrettyPrint) {
theEventWriter.setPrettyPrint(myPrettyPrint); theEventWriter.setPrettyPrint(myPrettyPrint);
} }
theEventWriter.init(); theEventWriter.init();
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, false); encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext);
theEventWriter.flush(); theEventWriter.flush();
} }
@Override @Override
protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException { protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
JsonLikeWriter eventWriter = createJsonWriter(theWriter); JsonLikeWriter eventWriter = createJsonWriter(theWriter);
doEncodeResourceToJsonLikeWriter(theResource, eventWriter); doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext);
} }
@Override @Override
@ -192,8 +192,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue, private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem, BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
boolean theForceEmpty) throws IOException { boolean theForceEmpty, EncodeContext theEncodeContext) throws IOException {
switch (theChildDef.getChildType()) { switch (theChildDef.getChildType()) {
case ID_DATATYPE: { case ID_DATATYPE: {
@ -265,7 +265,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else { } else {
theEventWriter.beginObject(); theEventWriter.beginObject();
} }
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem); encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem, theEncodeContext);
theEventWriter.endObject(); theEventWriter.endObject();
break; break;
} }
@ -283,7 +283,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
for (IBaseResource next : containedResources) { for (IBaseResource next : containedResources) {
IIdType resourceId = getContainedResources().getResourceId(next); IIdType resourceId = getContainedResources().getResourceId(next);
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue())); encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
} }
theEventWriter.endArray(); theEventWriter.endArray();
@ -311,7 +311,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
case RESOURCE: case RESOURCE:
IBaseResource resource = (IBaseResource) theNextValue; IBaseResource resource = (IBaseResource) theNextValue;
RuntimeResourceDefinition def = myContext.getResourceDefinition(resource); RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, true);
theEncodeContext.pushPath(def.getName(), true);
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, theEncodeContext);
theEncodeContext.popPath();
break; break;
case UNDECL_EXT: case UNDECL_EXT:
default: default:
@ -321,7 +325,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter, private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException { boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException {
{ {
String elementId = getCompositeElementId(theElement); String elementId = getCompositeElementId(theElement);
@ -331,14 +335,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
boolean haveWrittenExtensions = false; boolean haveWrittenExtensions = false;
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) { for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension") if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) { || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
if (!haveWrittenExtensions) { if (!haveWrittenExtensions) {
extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent); extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext);
haveWrittenExtensions = true; haveWrittenExtensions = true;
} }
continue; continue;
@ -361,7 +365,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype()); String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, theSubResource, nextChildElem, false); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false, theEncodeContext);
continue; continue;
} }
} }
@ -369,12 +373,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else if (nextChild instanceof RuntimeChildContainedResources) { } else if (nextChild instanceof RuntimeChildContainedResources) {
String childName = nextChild.getValidChildNames().iterator().next(); String childName = nextChild.getValidChildNames().iterator().next();
BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName); BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, theSubResource, nextChildElem, false); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false, theEncodeContext);
continue; continue;
} }
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
values = super.preProcessValues(nextChild, theResource, values, nextChildElem); values = super.preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
if (values == null || values.isEmpty()) { if (values == null || values.isEmpty()) {
continue; continue;
@ -407,6 +411,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
String childName = childNameAndDef.getChildName(); String childName = childNameAndDef.getChildName();
theEncodeContext.pushPath(childName, false);
BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE; boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
@ -452,18 +458,19 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) { if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
beginArray(theEventWriter, childName); beginArray(theEventWriter, childName);
inArray = true; inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) { } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources // suppress narratives from contained resources
} else { } else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource, nextChildElem, false); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false, theEncodeContext);
} }
currentChildName = childName; currentChildName = childName;
} else { } else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
} }
valueIdx++; valueIdx++;
theEncodeContext.popPath();
} }
if (inArray) { if (inArray) {
@ -525,7 +532,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
theEventWriter.endArray(); theEventWriter.endArray();
} }
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext);
if (inArray) { if (inArray) {
theEventWriter.endObject(); theEventWriter.endObject();
} }
@ -541,11 +548,10 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
} }
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource, private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException {
CompositeChildElement theParent) throws IOException, DataFormatException {
writeCommentsPreAndPost(theNextValue, theEventWriter); writeCommentsPreAndPost(theNextValue, theEventWriter);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext);
} }
@Override @Override
@ -558,27 +564,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
} }
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter); EncodeContext encodeContext = new EncodeContext();
String resourceName = myContext.getResourceDefinition(theResource).getName();
encodeContext.pushPath(resourceName, true);
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
} }
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull, private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
boolean theContainedResource, boolean theSubResource) throws IOException { boolean theContainedResource, EncodeContext theEncodeContext) throws IOException {
IIdType resourceId = null; IIdType resourceId = null;
// if (theResource instanceof IResource) {
// IResource res = (IResource) theResource;
// if (StringUtils.isNotBlank(res.getId().getIdPart())) {
// if (theContainedResource) {
// resourceId = res.getId().getIdPart();
// } else if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
// resourceId = res.getId().getIdPart();
// }
// }
// } else if (theResource instanceof IAnyResource) {
// IAnyResource res = (IAnyResource) theResource;
// if (/* theContainedResource && */StringUtils.isNotBlank(res.getIdElement().getIdPart())) {
// resourceId = res.getIdElement().getIdPart();
// }
// }
if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
resourceId = theResource.getIdElement(); resourceId = theResource.getIdElement();
@ -588,18 +582,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
if (!theContainedResource) { if (!theContainedResource) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) { if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) {
resourceId = null; resourceId = null;
} else if (!theSubResource && getEncodeForceResourceId() != null) { } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
resourceId = getEncodeForceResourceId(); resourceId = getEncodeForceResourceId();
} }
} }
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, theSubResource, resourceId); encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId, theEncodeContext);
} }
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull, private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException { boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException {
if (!theContainedResource) { if (!theContainedResource) {
super.containResourcesForEncoding(theResource); super.containResourcesForEncoding(theResource);
} }
@ -630,7 +624,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
writeCommentsPreAndPost(theResourceId, theEventWriter); writeCommentsPreAndPost(theResourceId, theEventWriter);
} }
if (haveExtension) { if (haveExtension) {
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
} }
theEventWriter.endObject(); theEventWriter.endObject();
} }
@ -644,7 +638,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
profiles = super.getProfileTagsForEncoding(resource, profiles); profiles = super.getProfileTagsForEncoding(resource, profiles);
TagList tags = getMetaTagsForEncoding(resource); TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
IdDt resourceId = resource.getId(); IdDt resourceId = resource.getId();
String versionIdPart = resourceId.getVersionIdPart(); String versionIdPart = resourceId.getVersionIdPart();
@ -672,7 +666,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
beginArray(theEventWriter, "security"); beginArray(theEventWriter, "security");
for (BaseCodingDt securityLabel : securityLabels) { for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.beginObject(); theEventWriter.beginObject();
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null); theEncodeContext.pushPath("security", false);
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.endObject(); theEventWriter.endObject();
} }
theEventWriter.endArray(); theEventWriter.endArray();
@ -693,23 +689,23 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endArray(); theEventWriter.endArray();
} }
addExtensionMetadata(theResDef, theResource, theContainedResource, theSubResource, extensionMetadataKeys, resDef, theEventWriter); addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
theEventWriter.endObject(); // end meta theEventWriter.endObject(); // end meta
} }
} }
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
theEventWriter.endObject(); theEventWriter.endObject();
} }
private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource, private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
boolean theContainedResource, boolean theSubResource, boolean theContainedResource,
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys, List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
RuntimeResourceDefinition resDef, RuntimeResourceDefinition resDef,
JsonLikeWriter theEventWriter) throws IOException { JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
if (extensionMetadataKeys.isEmpty()) { if (extensionMetadataKeys.isEmpty()) {
return; return;
} }
@ -718,7 +714,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) { for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue()); metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
} }
encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
} }
/** /**
@ -726,7 +722,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
* called _name): resource extensions, and extension extensions * called _name): resource extensions, and extension extensions
*/ */
private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent) throws IOException { IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException {
List<HeldExtension> extensions = new ArrayList<>(0); List<HeldExtension> extensions = new ArrayList<>(0);
List<HeldExtension> modifierExtensions = new ArrayList<>(0); List<HeldExtension> modifierExtensions = new ArrayList<>(0);
@ -739,7 +735,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
// Write the extensions // Write the extensions
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
} }
private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
@ -1260,18 +1256,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
List<HeldExtension> modifierExtensions) throws IOException { List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext) throws IOException {
if (extensions.isEmpty() == false) { if (extensions.isEmpty() == false) {
beginArray(theEventWriter, "extension"); beginArray(theEventWriter, "extension");
for (HeldExtension next : extensions) { for (HeldExtension next : extensions) {
next.write(resDef, theResource, theEventWriter); next.write(resDef, theResource, theEventWriter, theEncodeContext);
} }
theEventWriter.endArray(); theEventWriter.endArray();
} }
if (modifierExtensions.isEmpty() == false) { if (modifierExtensions.isEmpty() == false) {
beginArray(theEventWriter, "modifierExtension"); beginArray(theEventWriter, "modifierExtension");
for (HeldExtension next : modifierExtensions) { for (HeldExtension next : modifierExtensions) {
next.write(resDef, theResource, theEventWriter); next.write(resDef, theResource, theEventWriter, theEncodeContext);
} }
theEventWriter.endArray(); theEventWriter.endArray();
} }
@ -1334,7 +1330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
return url1.compareTo(url2); return url1.compareTo(url2);
} }
private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName) throws IOException { private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName, EncodeContext theEncodeContext) throws IOException {
if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) { if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0); final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0); final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
@ -1350,15 +1346,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
if (haveContent) { if (haveContent) {
beginObject(theEventWriter, '_' + childName); beginObject(theEventWriter, '_' + childName);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
theEventWriter.endObject(); theEventWriter.endObject();
} }
} }
} }
public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException { public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
if (myUndeclaredExtension != null) { if (myUndeclaredExtension != null) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension); writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext);
} else { } else {
theEventWriter.beginObject(); theEventWriter.beginObject();
@ -1372,7 +1368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
* *
* See #327 * See #327
*/ */
List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem); List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem, theEncodeContext);
// // Check for undeclared extensions on the declared extension // // Check for undeclared extensions on the declared extension
// // (grrrrrr....) // // (grrrrrr....)
@ -1391,18 +1387,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass()); BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null); extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext);
} else { } else {
String childName = myDef.getChildNameByDatatype(myValue.getClass()); String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext);
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName); managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext);
} }
theEventWriter.endObject(); theEventWriter.endObject();
} }
} }
private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext) throws IOException { private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext, EncodeContext theEncodeContext) throws IOException {
IBase value = ext.getValue(); IBase value = ext.getValue();
final String extensionUrl = getExtensionUrl(ext.getUrl()); final String extensionUrl = getExtensionUrl(ext.getUrl());
@ -1429,7 +1425,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
for (Object next : ext.getExtension()) { for (Object next : ext.getExtension()) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next); writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext);
} }
theEventWriter.endArray(); theEventWriter.endArray();
} else { } else {
@ -1438,7 +1434,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
* Pre-process value - This is called in case the value is a reference * Pre-process value - This is called in case the value is a reference
* since we might modify the text * since we might modify the text
*/ */
value = JsonParser.super.preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem).get(0); value = JsonParser.super.preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem, theEncodeContext).get(0);
RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition(); RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
String childName = extDef.getChildNameByDatatype(value.getClass()); String childName = extDef.getChildNameByDatatype(value.getClass());
@ -1449,8 +1445,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (childDef == null) { if (childDef == null) {
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
} }
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, myParent,false, theEncodeContext);
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName); managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext);
} }
// theEventWriter.name(myUndeclaredExtension.get); // theEventWriter.name(myUndeclaredExtension.get);

View File

@ -20,28 +20,32 @@ package ca.uhn.fhir.parser;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank; import ca.uhn.fhir.context.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import java.io.Reader; import ca.uhn.fhir.model.primitive.IdDt;
import java.io.StringReader; import ca.uhn.fhir.model.primitive.InstantDt;
import java.io.Writer; import ca.uhn.fhir.model.primitive.XhtmlDt;
import java.util.*; import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
import ca.uhn.fhir.util.XmlUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import javax.xml.stream.*; import javax.xml.stream.*;
import javax.xml.stream.events.*; import javax.xml.stream.events.*;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils; import static org.apache.commons.lang3.StringUtils.isBlank;
import org.hl7.fhir.instance.model.api.*; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.primitive.*;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.*;
/** /**
* This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
@ -52,14 +56,13 @@ public class XmlParser extends BaseParser /* implements IParser */ {
static final String ATOM_NS = "http://www.w3.org/2005/Atom"; static final String ATOM_NS = "http://www.w3.org/2005/Atom";
static final String FHIR_NS = "http://hl7.org/fhir"; static final String FHIR_NS = "http://hl7.org/fhir";
static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/"; static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
static final String RESREF_DISPLAY = "display"; static final String RESREF_DISPLAY = "display";
static final String RESREF_REFERENCE = "reference"; static final String RESREF_REFERENCE = "reference";
static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0"; static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
static final String XHTML_NS = "http://www.w3.org/1999/xhtml"; static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
// private static final Set<String> RESOURCE_NAMESPACES; // private static final Set<String> RESOURCE_NAMESPACES;
private FhirContext myContext; private FhirContext myContext;
private boolean myPrettyPrint; private boolean myPrettyPrint;
@ -101,12 +104,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
} }
@Override @Override
public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException { public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws DataFormatException {
XMLStreamWriter eventWriter; XMLStreamWriter eventWriter;
try { try {
eventWriter = createXmlWriter(theWriter); eventWriter = createXmlWriter(theWriter);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false); encodeResourceToXmlStreamWriter(theResource, eventWriter, false, theEncodeContext);
eventWriter.flush(); eventWriter.flush();
} catch (XMLStreamException e) { } catch (XMLStreamException e) {
throw new ConfigurationException("Failed to initialize STaX event factory", e); throw new ConfigurationException("Failed to initialize STaX event factory", e);
@ -123,7 +126,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
ourLog.trace("Entering XML parsing loop with state: {}", parserState); ourLog.trace("Entering XML parsing loop with state: {}", parserState);
try { try {
List<String> heldComments = new ArrayList<String>(1); List<String> heldComments = new ArrayList<>(1);
while (streamReader.hasNext()) { while (streamReader.hasNext()) {
XMLEvent nextEvent = streamReader.nextEvent(); XMLEvent nextEvent = streamReader.nextEvent();
@ -167,10 +170,8 @@ public class XmlParser extends BaseParser /* implements IParser */ {
heldComments.clear(); heldComments.clear();
} }
@SuppressWarnings("unchecked") for (Iterator<Attribute> attributes = elem.getAttributes(); attributes.hasNext(); ) {
Iterator<Attribute> attributes = elem.getAttributes(); Attribute next = attributes.next();
for (Iterator<Attribute> iter = attributes; iter.hasNext();) {
Attribute next = iter.next();
parserState.attributeValue(next.getName().getLocalPart(), next.getValue()); parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
} }
@ -215,7 +216,10 @@ public class XmlParser extends BaseParser /* implements IParser */ {
} }
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef, private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
String theExtensionUrl, boolean theIncludedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
theEncodeContext.pushPath(childName, false);
try {
if (theElement == null || theElement.isEmpty()) { if (theElement == null || theElement.isEmpty()) {
if (isChildContained(childDef, theIncludedResource)) { if (isChildContained(childDef, theIncludedResource)) {
// We still want to go in.. // We still want to go in..
@ -230,12 +234,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case ID_DATATYPE: { case ID_DATATYPE: {
IIdType value = IIdType.class.cast(theElement); IIdType value = IIdType.class.cast(theElement);
String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue(); String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
if (StringUtils.isNotBlank(encodedValue) || super.hasExtensions(value)) { if (StringUtils.isNotBlank(encodedValue) || !super.hasNoExtensions(value)) {
theEventWriter.writeStartElement(childName); theEventWriter.writeStartElement(childName);
if (StringUtils.isNotBlank(encodedValue)) { if (StringUtils.isNotBlank(encodedValue)) {
theEventWriter.writeAttribute("value", encodedValue); theEventWriter.writeAttribute("value", encodedValue);
} }
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource); encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
break; break;
@ -243,7 +247,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case PRIMITIVE_DATATYPE: { case PRIMITIVE_DATATYPE: {
IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement); IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
String value = pd.getValueAsString(); String value = pd.getValueAsString();
if (value != null || super.hasExtensions(pd)) { if (value != null || !super.hasNoExtensions(pd)) {
theEventWriter.writeStartElement(childName); theEventWriter.writeStartElement(childName);
String elementId = getCompositeElementId(theElement); String elementId = getCompositeElementId(theElement);
if (isNotBlank(elementId)) { if (isNotBlank(elementId)) {
@ -252,7 +256,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (value != null) { if (value != null) {
theEventWriter.writeAttribute("value", value); theEventWriter.writeAttribute("value", value);
} }
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource); encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
break; break;
@ -267,7 +271,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (isNotBlank(theExtensionUrl)) { if (isNotBlank(theExtensionUrl)) {
theEventWriter.writeAttribute("url", theExtensionUrl); theEventWriter.writeAttribute("url", theExtensionUrl);
} }
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent); encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
break; break;
} }
@ -281,7 +285,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
for (IBaseResource next : getContainedResources().getContainedResources()) { for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next); IIdType resourceId = getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained"); theEventWriter.writeStartElement("contained");
encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue())); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
break; break;
@ -289,7 +293,10 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case RESOURCE: { case RESOURCE: {
theEventWriter.writeStartElement(childName); theEventWriter.writeStartElement(childName);
IBaseResource resource = (IBaseResource) theElement; IBaseResource resource = (IBaseResource) theElement;
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true); String resourceName = myContext.getResourceDefinition(resource).getName();
theEncodeContext.pushPath(resourceName, true);
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
break; break;
} }
@ -318,12 +325,16 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPost(theEventWriter, theElement); writeCommentsPost(theEventWriter, theElement);
} finally {
theEncodeContext.popPath();
} }
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) }
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext)
throws XMLStreamException, DataFormatException { throws XMLStreamException, DataFormatException {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) { for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
@ -353,17 +364,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype()); String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, theSubResource, nextChildElem); encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
continue; continue;
} }
} }
if (nextChild instanceof RuntimeChildContainedResources) { if (nextChild instanceof RuntimeChildContainedResources) {
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, theSubResource, nextChildElem); encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem, theEncodeContext);
} else { } else {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
values = super.preProcessValues(nextChild, theResource, values, nextChildElem); values = super.preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
if (values == null || values.isEmpty()) { if (values == null || values.isEmpty()) {
continue; continue;
@ -383,7 +394,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
if (extensionUrl != null && childName.equals("extension") == false) { if (extensionUrl != null && childName.equals("extension") == false) {
encodeExtension(theResource, theEventWriter, theContainedResource, theSubResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef); encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef, theEncodeContext);
} else if (nextChild instanceof RuntimeChildExtension) { } else if (nextChild instanceof RuntimeChildExtension) {
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue; IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
if ((extension.getValue() == null || extension.getValue().isEmpty())) { if ((extension.getValue() == null || extension.getValue().isEmpty())) {
@ -391,18 +402,19 @@ public class XmlParser extends BaseParser /* implements IParser */ {
continue; continue;
} }
} }
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, theSubResource, nextChildElem); encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem, theEncodeContext);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) { } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources // suppress narratives from contained resources
} else { } else {
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, theSubResource, nextChildElem); encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem, theEncodeContext);
} }
} }
} }
} }
} }
private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef) private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef, EncodeContext theEncodeContext)
throws XMLStreamException { throws XMLStreamException {
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
if (extDef.isModifier()) { if (extDef.isModifier()) {
@ -417,27 +429,27 @@ public class XmlParser extends BaseParser /* implements IParser */ {
} }
theEventWriter.writeAttribute("url", extensionUrl); theEventWriter.writeAttribute("url", extensionUrl);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, theSubResource, nextChildElem); encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException { private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
if (theElement instanceof ISupportsUndeclaredExtensions) { if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource); encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theEncodeContext);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource); encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource, theEncodeContext);
} }
if (theElement instanceof IBaseHasExtensions) { if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions res = (IBaseHasExtensions) theElement; IBaseHasExtensions res = (IBaseHasExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource); encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext);
} }
if (theElement instanceof IBaseHasModifierExtensions) { if (theElement instanceof IBaseHasModifierExtensions) {
IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource,theSubResource); encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource, theEncodeContext);
} }
} }
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException { private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
IIdType resourceId = null; IIdType resourceId = null;
if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
@ -448,17 +460,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
} }
if (!theIncludedResource) { if (!theIncludedResource) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) { if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) {
resourceId = null; resourceId = null;
} else if (theSubResource == false && getEncodeForceResourceId() != null) { } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
resourceId = getEncodeForceResourceId(); resourceId = getEncodeForceResourceId();
} }
} }
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, theSubResource, resourceId); encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext);
} }
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws XMLStreamException { private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws XMLStreamException {
if (!theContainedResource) { if (!theContainedResource) {
super.containResourcesForEncoding(theResource); super.containResourcesForEncoding(theResource);
} }
@ -477,12 +489,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPre(theEventWriter, theResourceId); writeCommentsPre(theEventWriter, theResourceId);
theEventWriter.writeStartElement("id"); theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart()); theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false); encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId); writeCommentsPost(theEventWriter, theResourceId);
} }
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
} else { } else {
@ -495,7 +507,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPost(theEventWriter, theResourceId);*/ writeCommentsPost(theEventWriter, theResourceId);*/
theEventWriter.writeStartElement("id"); theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart()); theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false); encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId); writeCommentsPost(theEventWriter, theResourceId);
} }
@ -510,7 +522,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
profiles = super.getProfileTagsForEncoding(resource, profiles); profiles = super.getProfileTagsForEncoding(resource, profiles);
TagList tags = getMetaTagsForEncoding((resource)); TagList tags = getMetaTagsForEncoding((resource), theEncodeContext);
if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) { if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
theEventWriter.writeStartElement("meta"); theEventWriter.writeStartElement("meta");
@ -526,7 +538,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
} }
for (BaseCodingDt securityLabel : securityLabels) { for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.writeStartElement("security"); theEventWriter.writeStartElement("security");
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null); encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
if (tags != null) { if (tags != null) {
@ -549,7 +561,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
} else { } else {
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
} }
} }
@ -557,7 +569,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, boolean theSubResource) private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, EncodeContext theEncodeContext)
throws XMLStreamException, DataFormatException { throws XMLStreamException, DataFormatException {
for (IBaseExtension<?, ?> next : theExtensions) { for (IBaseExtension<?, ?> next : theExtensions) {
if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
@ -593,11 +605,11 @@ public class XmlParser extends BaseParser /* implements IParser */ {
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
} }
} }
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null); encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null, theEncodeContext);
} }
// child extensions // child extensions
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource); encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();

View File

@ -137,6 +137,7 @@ public class Constants {
public static final String PARAM_COUNT = "_count"; public static final String PARAM_COUNT = "_count";
public static final String PARAM_DELETE = "_delete"; public static final String PARAM_DELETE = "_delete";
public static final String PARAM_ELEMENTS = "_elements"; public static final String PARAM_ELEMENTS = "_elements";
public static final String PARAM_ELEMENTS_EXCLUDE_MODIFIER = ":exclude";
public static final String PARAM_FORMAT = "_format"; public static final String PARAM_FORMAT = "_format";
public static final String PARAM_HAS = "_has"; public static final String PARAM_HAS = "_has";
public static final String PARAM_HISTORY = "_history"; public static final String PARAM_HISTORY = "_history";

View File

@ -106,6 +106,9 @@ public enum EncodingEnum {
myFormatContentType = theFormatContentType; myFormatContentType = theFormatContentType;
} }
/**
* Returns <code>xml</code> or <code>json</code> as used on the <code>_format</code> search parameter
*/
public String getFormatContentType() { public String getFormatContentType() {
return myFormatContentType; return myFormatContentType;
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.util;
* #L% * #L%
*/ */
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -55,6 +56,24 @@ public class PortUtil {
server = new ServerSocket(0); server = new ServerSocket(0);
server.setReuseAddress(true); server.setReuseAddress(true);
int port = server.getLocalPort(); int port = server.getLocalPort();
/*
* Try to connect to the newly allocated port to make sure
* it's free
*/
for (int i = 0; i < 10; i++) {
try {
Socket client = new Socket();
client.connect(new InetSocketAddress(port), 1000);
break;
} catch (Exception e) {
if (i == 9) {
throw new InternalErrorException("Can not connect to port: " + port);
}
Thread.sleep(250);
}
}
server.close(); server.close();
/* /*

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.util;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import static org.junit.Assert.*;
public class PortUtilTest {
@Test
public void testFindFreePort() throws IOException {
int port = PortUtil.findFreePort();
// First bind should succeed, second bind should fail
try (ServerSocket ss = new ServerSocket()) {
ss.bind(new InetSocketAddress("0.0.0.0", port));
try (ServerSocket ss2 = new ServerSocket()) {
ss2.bind(new InetSocketAddress("0.0.0.0", port));
fail();
} catch (IOException e) {
// good
}
}
}
}

View File

@ -84,7 +84,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
/** /**
* This method returns the server base, including the resource path. * This method returns the server base, including the resource path.
* {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()} * {@link UriInfo#getBaseUri() UriInfo#getBaseUri()}
* *
* @return the ascii string for the base resource provider path * @return the ascii string for the base resource provider path
*/ */
@ -119,6 +119,14 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
return ETagSupportEnum.DISABLED; return ETagSupportEnum.DISABLED;
} }
/**
* DEFAULT = {@link ElementsSupportEnum#STANDARD}
*/
@Override
public ElementsSupportEnum getElementsSupport() {
return ElementsSupportEnum.STANDARD;
}
@Override @Override
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return CTX; return CTX;
@ -243,14 +251,6 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
return true; return true;
} }
/**
* DEFAULT = false
*/
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
/** /**
* Set the headers * Set the headers
* *

View File

@ -150,11 +150,6 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider<Pati
return true; return true;
} }
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
@GET @GET
@Path("/{id}/$firstVersion") @Path("/{id}/$firstVersion")
public Response operationFirstVersionUsingGet(@PathParam("id") String id) throws IOException { public Response operationFirstVersionUsingGet(@PathParam("id") String id) throws IOException {

View File

@ -151,11 +151,6 @@ public class JaxRsPatientRestProviderDstu3 extends AbstractJaxRsResourceProvider
return true; return true;
} }
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
@GET @GET
@Path("/{id}/$firstVersion") @Path("/{id}/$firstVersion")
public Response operationFirstVersionUsingGet(@PathParam("id") String id) throws IOException { public Response operationFirstVersionUsingGet(@PathParam("id") String id) throws IOException {

View File

@ -102,10 +102,10 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
// Only delete if we don't have results left in this search // Only delete if we don't have results left in this search
if (resultPids.getNumberOfElements() < max) { if (resultPids.getNumberOfElements() < max) {
ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), new InstantType(searchToDelete.getCreated()), new InstantType(searchToDelete.getSearchLastReturned())); ourLog.debug("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), new InstantType(searchToDelete.getCreated()), new InstantType(searchToDelete.getSearchLastReturned()));
mySearchDao.deleteByPid(searchToDelete.getId()); mySearchDao.deleteByPid(searchToDelete.getId());
} else { } else {
ourLog.info("Purged {} search results for deleted search {}/{}", resultPids.getSize(), searchToDelete.getId(), searchToDelete.getUuid()); ourLog.debug("Purged {} search results for deleted search {}/{}", resultPids.getSize(), searchToDelete.getId(), searchToDelete.getUuid());
} }
}); });
} }
@ -149,7 +149,7 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
int count = toDelete.getContent().size(); int count = toDelete.getContent().size();
if (count > 0) { if (count > 0) {
long total = tt.execute(t -> mySearchDao.count()); long total = tt.execute(t -> mySearchDao.count());
ourLog.info("Deleted {} searches, {} remaining", count, total); ourLog.debug("Deleted {} searches, {} remaining", count, total);
} }
} }

View File

@ -46,7 +46,6 @@ public class SystemProviderTransactionSearchDstu2Test extends BaseJpaDstu2Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@After @After
public void after() { public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
} }

View File

@ -47,7 +47,6 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@After @After
public void after() { public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
} }

View File

@ -62,7 +62,6 @@ public class EmptyIndexesR4Test extends BaseJpaR4Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@After @After
public void after() { public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
} }

View File

@ -67,7 +67,6 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@After @After
public void after() { public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
} }
@ -250,7 +249,6 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void testResponseUsesCorrectContentType() throws Exception { public void testResponseUsesCorrectContentType() throws Exception {
myRestServer.setUseBrowserFriendlyContentTypes(true);
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
HttpGet get = new HttpGet(ourServerBase); HttpGet get = new HttpGet(ourServerBase);

View File

@ -51,7 +51,6 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@After @After
public void after() { public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction()); myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
} }

View File

@ -15,10 +15,7 @@ import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
@ -204,6 +201,11 @@ public class TestRestfulServer extends RestfulServer {
setDefaultPrettyPrint(true); setDefaultPrettyPrint(true);
setDefaultResponseEncoding(EncodingEnum.JSON); setDefaultResponseEncoding(EncodingEnum.JSON);
/*
* Use extended support for the _elements parameter
*/
setElementsSupport(ElementsSupportEnum.EXTENDED);
/* /*
* The server's base URL (e.g. http://fhirtest.uhn.ca/baseDstu2) is * The server's base URL (e.g. http://fhirtest.uhn.ca/baseDstu2) is
* pulled from a system property, which is helpful if you want to try * pulled from a system property, which is helpful if you want to try

View File

@ -0,0 +1,22 @@
package ca.uhn.fhir.rest.server;
/**
* @see <a href="http://hapifhir.io/doc_rest_server.html#extended_elements_support">Extended Elements Support</a>
*/
public enum ElementsSupportEnum {
/**
* The server will support only the FHIR standard features for the <code>_elements</code>
* parameter.
*
* @see <a href="http://hl7.org/fhir/search.html#elements">http://hl7.org/fhir/search.html#elements</a>
*/
STANDARD,
/**
* The server will support both the standard features as well as support for elements
* exclusion.
*/
EXTENDED
}

View File

@ -49,6 +49,13 @@ public interface IRestfulServerDefaults {
*/ */
ETagSupportEnum getETagSupport(); ETagSupportEnum getETagSupport();
/**
* @return Returns the support option for the <code>_elements</code> parameter on search
* and read operations.
* @see <a href="http://hapifhir.io/doc_rest_server.html#extended_elements_support">Extended Elements Support</a>
*/
ElementsSupportEnum getElementsSupport();
/** /**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to * providers should generally use this context if one is needed, as opposed to
@ -78,12 +85,6 @@ public interface IRestfulServerDefaults {
*/ */
boolean isDefaultPrettyPrint(); boolean isDefaultPrettyPrint();
/**
* @return If <code>true</code> the server will use browser friendly content-types (instead of standard FHIR ones)
* when it detects that the request is coming from a browser
* instead of a FHIR
*/
boolean isUseBrowserFriendlyContentTypes();
} }

View File

@ -40,7 +40,6 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.server.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -126,10 +125,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
private String myServerVersion = createPoweredByHeaderProductVersion(); private String myServerVersion = createPoweredByHeaderProductVersion();
private boolean myStarted; private boolean myStarted;
private boolean myUncompressIncomingContents = true; private boolean myUncompressIncomingContents = true;
private boolean myUseBrowserFriendlyContentTypes;
private ITenantIdentificationStrategy myTenantIdentificationStrategy; private ITenantIdentificationStrategy myTenantIdentificationStrategy;
private Date myConformanceDate; private Date myConformanceDate;
private PreferReturnEnum myDefaultPreferReturn = DEFAULT_PREFER_RETURN; private PreferReturnEnum myDefaultPreferReturn = DEFAULT_PREFER_RETURN;
private ElementsSupportEnum myElementsSupport = ElementsSupportEnum.EXTENDED;
/** /**
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or * Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
@ -500,6 +499,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return myETagSupport; return myETagSupport;
} }
@Override
public ElementsSupportEnum getElementsSupport() {
return myElementsSupport;
}
/**
* Sets the elements support mode.
*
* @see <a href="http://hapifhir.io/doc_rest_server.html#extended_elements_support">Extended Elements Support</a>
*/
public void setElementsSupport(ElementsSupportEnum theElementsSupport) {
Validate.notNull(theElementsSupport, "theElementsSupport must not be null");
myElementsSupport = theElementsSupport;
}
/** /**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is * Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT} * {@link #DEFAULT_ETAG_SUPPORT}
@ -1025,6 +1039,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
*/ */
requestDetails.removeParameter(Constants.PARAM_SUMMARY); requestDetails.removeParameter(Constants.PARAM_SUMMARY);
requestDetails.removeParameter(Constants.PARAM_ELEMENTS); requestDetails.removeParameter(Constants.PARAM_ELEMENTS);
requestDetails.removeParameter(Constants.PARAM_ELEMENTS + Constants.PARAM_ELEMENTS_EXCLUDE_MODIFIER);
/* /*
* If nobody handles it, default behaviour is to stream back the OperationOutcome to the client. * If nobody handles it, default behaviour is to stream back the OperationOutcome to the client.
@ -1251,26 +1266,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
myUncompressIncomingContents = theUncompressIncomingContents; myUncompressIncomingContents = theUncompressIncomingContents;
} }
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}
public void populateRequestDetailsFromRequestPath(RequestDetails theRequestDetails, String theRequestPath) { public void populateRequestDetailsFromRequestPath(RequestDetails theRequestDetails, String theRequestPath) {
UrlPathTokenizer tok = new UrlPathTokenizer(theRequestPath); UrlPathTokenizer tok = new UrlPathTokenizer(theRequestPath);

View File

@ -46,6 +46,7 @@ import java.io.Writer;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.*;
@ -54,7 +55,7 @@ public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<String>(Arrays.asList("Bundle", "*.text", "*.(mandatory)")); private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<>(Arrays.asList("*.text", "*.id", "*.meta", "*.(mandatory)"));
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<FhirVersionEnum, FhirContext>()); private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<FhirVersionEnum, FhirContext>());
private enum NarrativeModeEnum { private enum NarrativeModeEnum {
@ -125,13 +126,15 @@ public class RestfulServerUtils {
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails); Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails);
// _elements // _elements
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails); Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails, false);
if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) { if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) {
throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters"); throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters");
} }
Set<String> elementsAppliesTo = null;
if (elements != null && isNotBlank(theRequestDetails.getResourceName())) { // _elements:exclude
elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName()); Set<String> elementsExclude = ElementsParameter.getElementsValueOrNull(theRequestDetails, true);
if (elementsExclude != null) {
parser.setDontEncodeElements(elementsExclude);
} }
if (summaryMode != null) { if (summaryMode != null) {
@ -139,15 +142,27 @@ public class RestfulServerUtils {
parser.setEncodeElements(Collections.singleton("Bundle.total")); parser.setEncodeElements(Collections.singleton("Bundle.total"));
} else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) { } else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
} else { } else {
parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA)); parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA));
parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE)); parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE));
} }
} }
if (elements != null && elements.size() > 0) { if (elements != null && elements.size() > 0) {
String elementsAppliesTo = "*";
if (isNotBlank(theRequestDetails.getResourceName())) {
elementsAppliesTo = theRequestDetails.getResourceName();
}
Set<String> newElements = new HashSet<>(); Set<String> newElements = new HashSet<>();
for (String next : elements) { for (String next : elements) {
newElements.add("*." + next); if (isNotBlank(next)) {
if (Character.isUpperCase(next.charAt(0))) {
newElements.add(next);
} else {
newElements.add(elementsAppliesTo + "." + next);
}
}
} }
/* /*
@ -158,13 +173,6 @@ public class RestfulServerUtils {
* the client has explicitly scoped the Bundle * the client has explicitly scoped the Bundle
* (i.e. with Bundle.total or something like that) * (i.e. with Bundle.total or something like that)
*/ */
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
boolean haveExplicitBundleElement = false; boolean haveExplicitBundleElement = false;
for (String next : newElements) { for (String next : newElements) {
if (next.startsWith("Bundle.")) { if (next.startsWith("Bundle.")) {
@ -172,6 +180,13 @@ public class RestfulServerUtils {
break; break;
} }
} }
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
if (!haveExplicitBundleElement) { if (!haveExplicitBundleElement) {
parser.setEncodeElementsAppliesToChildResourcesOnly(true); parser.setEncodeElementsAppliesToChildResourcesOnly(true);
} }
@ -181,26 +196,28 @@ public class RestfulServerUtils {
} }
parser.setEncodeElements(newElements); parser.setEncodeElements(newElements);
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
} }
} }
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint, public static String createPagingLink(Set<Include> theIncludes, RequestDetails theRequestDetails, String theSearchId, int theOffset, int theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
BundleTypeEnum theBundleType) { BundleTypeEnum theBundleType) {
return createPagingLink(theIncludes, theServerBase, theSearchId, theOffset, theCount, theRequestParameters, thePrettyPrint, return createPagingLink(theIncludes, theRequestDetails, theSearchId, theOffset, theCount, theRequestParameters, thePrettyPrint,
theBundleType, null); theBundleType, null);
} }
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, String thePageId, Map<String, String[]> theRequestParameters, boolean thePrettyPrint, public static String createPagingLink(Set<Include> theIncludes, RequestDetails theRequestDetails, String theSearchId, String thePageId, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
BundleTypeEnum theBundleType) { BundleTypeEnum theBundleType) {
return createPagingLink(theIncludes, theServerBase, theSearchId, null, null, theRequestParameters, thePrettyPrint, return createPagingLink(theIncludes, theRequestDetails, theSearchId, null, null, theRequestParameters, thePrettyPrint,
theBundleType, thePageId); theBundleType, thePageId);
} }
private static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, Integer theOffset, Integer theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint, private static String createPagingLink(Set<Include> theIncludes, RequestDetails theRequestDetails, String theSearchId, Integer theOffset, Integer theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
BundleTypeEnum theBundleType, String thePageId) { BundleTypeEnum theBundleType, String thePageId) {
String serverBase = theRequestDetails.getFhirServerBase();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(theServerBase); b.append(serverBase);
b.append('?'); b.append('?');
b.append(Constants.PARAM_PAGINGACTION); b.append(Constants.PARAM_PAGINGACTION);
b.append('='); b.append('=');
@ -258,16 +275,33 @@ public class RestfulServerUtils {
b.append(theBundleType.getCode()); b.append(theBundleType.getCode());
} }
String paramName = Constants.PARAM_ELEMENTS; // _elements
String[] params = theRequestParameters.get(paramName); Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails, false);
if (params != null) { if (elements != null) {
for (String nextValue : params) {
if (isNotBlank(nextValue)) {
b.append('&'); b.append('&');
b.append(paramName); b.append(Constants.PARAM_ELEMENTS);
b.append('='); b.append('=');
b.append(UrlUtil.escapeUrlParam(nextValue)); String nextValue = elements
.stream()
.sorted()
.map(UrlUtil::escapeUrlParam)
.collect(Collectors.joining(","));
b.append(nextValue);
} }
// _elements:exclude
if (theRequestDetails.getServer().getElementsSupport() == ElementsSupportEnum.EXTENDED) {
Set<String> elementsExclude = ElementsParameter.getElementsValueOrNull(theRequestDetails, true);
if (elementsExclude != null) {
b.append('&');
b.append(Constants.PARAM_ELEMENTS + Constants.PARAM_ELEMENTS_EXCLUDE_MODIFIER);
b.append('=');
String nextValue = elementsExclude
.stream()
.sorted()
.map(UrlUtil::escapeUrlParam)
.collect(Collectors.joining(","));
b.append(nextValue);
} }
} }

View File

@ -192,19 +192,19 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
// We're doing named pages // We're doing named pages
searchId = theResult.getUuid(); searchId = theResult.getUuid();
if (isNotBlank(theResult.getNextPageId())) { if (isNotBlank(theResult.getNextPageId())) {
linkNext = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theResult.getNextPageId(), theRequest.getParameters(), prettyPrint, theBundleType); linkNext = RestfulServerUtils.createPagingLink(theIncludes, theRequest, searchId, theResult.getNextPageId(), theRequest.getParameters(), prettyPrint, theBundleType);
} }
if (isNotBlank(theResult.getPreviousPageId())) { if (isNotBlank(theResult.getPreviousPageId())) {
linkPrev = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theResult.getPreviousPageId(), theRequest.getParameters(), prettyPrint, theBundleType); linkPrev = RestfulServerUtils.createPagingLink(theIncludes, theRequest, searchId, theResult.getPreviousPageId(), theRequest.getParameters(), prettyPrint, theBundleType);
} }
} else if (searchId != null) { } else if (searchId != null) {
// We're doing offset pages // We're doing offset pages
if (numTotalResults == null || theOffset + numToReturn < numTotalResults) { if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
linkNext = (RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theOffset + numToReturn, numToReturn, theRequest.getParameters(), prettyPrint, theBundleType)); linkNext = (RestfulServerUtils.createPagingLink(theIncludes, theRequest, searchId, theOffset + numToReturn, numToReturn, theRequest.getParameters(), prettyPrint, theBundleType));
} }
if (theOffset > 0) { if (theOffset > 0) {
int start = Math.max(0, theOffset - theLimit); int start = Math.max(0, theOffset - theLimit);
linkPrev = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, start, theLimit, theRequest.getParameters(), prettyPrint, theBundleType); linkPrev = RestfulServerUtils.createPagingLink(theIncludes, theRequest, searchId, start, theLimit, theRequest.getParameters(), prettyPrint, theBundleType);
} }
} }

View File

@ -19,20 +19,24 @@ package ca.uhn.fhir.rest.server.method;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.lang.reflect.Method;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.binder.CollectionBinder; import ca.uhn.fhir.rest.param.binder.CollectionBinder;
import ca.uhn.fhir.rest.server.ElementsSupportEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ElementsParameter implements IParameter { public class ElementsParameter implements IParameter {
@ -42,7 +46,7 @@ public class ElementsParameter implements IParameter {
@Override @Override
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException { public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
Set<String> value = getElementsValueOrNull(theRequest); Set<String> value = getElementsValueOrNull(theRequest, false);
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
return null; return null;
} }
@ -62,32 +66,6 @@ public class ElementsParameter implements IParameter {
} }
} }
public static Set<String> getElementsValueOrNull(RequestDetails theRequest) {
String[] summary = theRequest.getParameters().get(Constants.PARAM_ELEMENTS);
if (summary != null && summary.length > 0) {
Set<String> retVal = new HashSet<String>();
for (String next : summary) {
StringTokenizer tok = new StringTokenizer(next, ",");
while (tok.hasMoreTokens()) {
String token = tok.nextToken();
if (isNotBlank(token)) {
retVal.add(token);
}
}
}
if (retVal.isEmpty()) {
return null;
}
// Always include the meta element even for subsetted values
retVal.add("meta");
return retVal;
}
return null;
}
@Override @Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) { public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
if (theOuterCollectionType != null) { if (theOuterCollectionType != null) {
@ -99,4 +77,44 @@ public class ElementsParameter implements IParameter {
} }
} }
public static Set<String> getElementsValueOrNull(RequestDetails theRequest, boolean theExclude) {
boolean standardMode = theRequest.getServer().getElementsSupport() != ElementsSupportEnum.EXTENDED;
if (theExclude && standardMode) {
return null;
}
String paramName = Constants.PARAM_ELEMENTS;
if (theExclude) {
paramName = Constants.PARAM_ELEMENTS + Constants.PARAM_ELEMENTS_EXCLUDE_MODIFIER;
}
String[] summary = theRequest.getParameters().get(paramName);
if (summary != null && summary.length > 0) {
Set<String> retVal = new HashSet<String>();
for (String next : summary) {
StringTokenizer tok = new StringTokenizer(next, ",");
while (tok.hasMoreTokens()) {
String token = tok.nextToken();
if (isNotBlank(token)) {
if (token.contains(".") && standardMode) {
continue;
}
retVal.add(token);
}
}
}
if (retVal.isEmpty()) {
return null;
}
// Always include the meta element even for subsetted values
if (!theExclude) {
retVal.add("meta");
}
return retVal;
}
return null;
}
} }

View File

@ -1011,6 +1011,7 @@ public class JsonParserDstu2_1Test {
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta"))); assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
} }
} }

View File

@ -1630,7 +1630,7 @@ public class XmlParserDstu2_1Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setDontEncodeElements(Sets.newHashSet("Patient.meta")); p.setDontEncodeElements(Sets.newHashSet("Patient.meta"));
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(patient); String out = p.encodeResourceToString(patient);
ourLog.info(out); ourLog.info(out);
@ -1639,6 +1639,7 @@ public class XmlParserDstu2_1Test {
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta"))); assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
} }
} }
@ -1667,7 +1668,6 @@ public class XmlParserDstu2_1Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1679,7 +1679,6 @@ public class XmlParserDstu2_1Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -2701,12 +2700,6 @@ public class XmlParserDstu2_1Test {
assertTrue(d.toString(), !d.hasDifferences()); assertTrue(d.toString(), !d.hasDifferences());
} }
public static void main(String[] args) {
IGenericClient c = ourCtx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
// c.registerInterceptor(new LoggingInterceptor(true));
c.read().resource("Patient").withId("324").execute();
}
@ResourceDef(name = "Patient") @ResourceDef(name = "Patient")
public static class TestPatientFor327 extends Patient { public static class TestPatientFor327 extends Patient {

View File

@ -1769,7 +1769,7 @@ public class XmlParserDstu2Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name", "Bundle.entry"))); p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name", "Bundle.entry")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1780,8 +1780,7 @@ public class XmlParserDstu2Test {
} }
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1792,8 +1791,7 @@ public class XmlParserDstu2Test {
} }
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient"))); p.setEncodeElements(new HashSet<>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1818,7 +1816,7 @@ public class XmlParserDstu2Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Bundle.entry", "*.text", "*.(mandatory)"))); p.setEncodeElements(new HashSet<>(Arrays.asList("Bundle.entry", "*.text", "*.(mandatory)")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);

View File

@ -1,178 +0,0 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.MaritalStatusCodesEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class ElementsParamTest {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static Set<String> ourLastElements;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElementsParamTest.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastElements = null;
}
@Test
public void testReadSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_elements=name,maritalStatus");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(Constants.CT_FHIR_XML + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
assertThat(responseContent, (containsString("family")));
assertThat(responseContent, (containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "name", "maritalStatus"));
}
@Test
public void testReadSummaryTrue() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_elements=name");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(Constants.CT_FHIR_XML + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
assertThat(responseContent, (containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "name"));
}
@Test
public void testSearchSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_elements=name,maritalStatus");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus"));
assertThat(ourLastElements, containsInAnyOrder("meta", "name", "maritalStatus"));
}
@Test
public void testSearchSummaryText() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_elements=text");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString("THE DIV")));
assertThat(responseContent, not(containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "text"));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Read
public Patient read(@IdParam IdDt theId, @Elements Set<String> theElements) {
ourLastElements = theElements;
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
patient.getText().setDiv("<div>THE DIV</div>");
patient.addName().addFamily("FAMILY");
patient.setMaritalStatus(MaritalStatusCodesEnum.D);
return patient;
}
@Search()
public Patient search(@Elements Set<String> theElements) {
ourLastElements = theElements;
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
patient.getText().setDiv("<div>THE DIV</div>");
patient.addName().addFamily("FAMILY");
patient.setMaritalStatus(MaritalStatusCodesEnum.D);
return patient;
}
}
}

View File

@ -1232,6 +1232,7 @@ public class JsonParserDstu3Test {
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta"))); assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
} }
} }

View File

@ -1862,6 +1862,7 @@ public class XmlParserDstu3Test {
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta"))); assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
} }
} }
@ -1890,7 +1891,6 @@ public class XmlParserDstu3Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1902,7 +1902,6 @@ public class XmlParserDstu3Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);

View File

@ -8,6 +8,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -252,6 +253,7 @@ public class JsonParserR4Test {
} }
@Test @Test
@Ignore
public void testExcludeRootStuff() { public void testExcludeRootStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true); IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>(); Set<String> excludes = new HashSet<>();

View File

@ -0,0 +1,320 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class ElementsParamR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElementsParamR4Test.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static Set<String> ourLastElements;
private static int ourPort;
private static Server ourServer;
private static Procedure ourNextProcedure;
private static RestfulServer ourServlet;
@Before
public void before() {
ourLastElements = null;
ourNextProcedure = null;
ourServlet.setElementsSupport(new RestfulServer().getElementsSupport());
}
@Test
public void testReadSummaryData() throws Exception {
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Patient/1?_elements=name,maritalStatus",
Patient.class,
patient -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patient")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
assertThat(responseContent, (containsString("family")));
assertThat(responseContent, (containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "name", "maritalStatus"));
}
);
}
@Test
public void testReadSummaryTrue() throws Exception {
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Patient/1?_elements=name",
Patient.class,
patient -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
assertThat(responseContent, (containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "name"));
}
);
}
@Test
public void testSearchSummaryData() throws Exception {
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Patient?_elements=name,maritalStatus",
bundle -> {
assertEquals("1", bundle.getTotalElement().getValueAsString());
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle.getEntry().get(0).getResource());
assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus"));
assertThat(ourLastElements, containsInAnyOrder("meta", "name", "maritalStatus"));
}
);
}
@Test
public void testSearchSummaryText() throws Exception {
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Patient?_elements=text&_pretty=true",
bundle -> {
assertEquals("1", bundle.getTotalElement().getValueAsString());
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle.getEntry().get(0).getResource());
assertThat(responseContent, containsString("THE DIV"));
assertThat(responseContent, not(containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastElements, containsInAnyOrder("meta", "text"));
}
);
}
/**
* By default the elements apply only to the focal resource in a search
* and not any included resources
*/
@Test
public void testStandardElementsFilter() throws IOException {
createProcedureWithLongChain();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_include=*&_elements=reasonCode,status",
bundle -> {
Procedure procedure = (Procedure) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", procedure.getMeta().getTag().get(0).getCode());
assertEquals("REASON_CODE", procedure.getReasonCode().get(0).getCoding().get(0).getCode());
assertEquals(0, procedure.getUsedCode().size());
DiagnosticReport dr = (DiagnosticReport) bundle.getEntry().get(1).getResource();
assertEquals(0, dr.getMeta().getTag().size());
assertEquals("Observation/OBSA", dr.getResult().get(0).getReference());
Observation obs = (Observation ) bundle.getEntry().get(2).getResource();
assertEquals(0, obs.getMeta().getTag().size());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals("1234-5", obs.getCode().getCoding().get(0).getCode());
});
}
@Test
public void testMultiResourceElementsFilter() throws IOException {
createProcedureWithLongChain();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_include=*&_elements=Procedure.reasonCode,Observation.status,Observation.subject,Observation.value",
bundle -> {
Procedure procedure = (Procedure) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", procedure.getMeta().getTag().get(0).getCode());
assertEquals("REASON_CODE", procedure.getReasonCode().get(0).getCoding().get(0).getCode());
assertEquals(0, procedure.getUsedCode().size());
DiagnosticReport dr = (DiagnosticReport) bundle.getEntry().get(1).getResource();
assertEquals(0, dr.getMeta().getTag().size());
Observation obs = (Observation ) bundle.getEntry().get(2).getResource();
assertEquals("SUBSETTED", obs.getMeta().getTag().get(0).getCode());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals(0, obs.getCode().getCoding().size());
assertEquals("STRING VALUE", obs.getValueStringType().getValue());
});
}
@Test
public void testMultiResourceElementsFilterWithMetadataExcluded() throws IOException {
createProcedureWithLongChain();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_include=*&_elements=Procedure.reasonCode,Observation.status,Observation.subject,Observation.value&_elements:exclude=*.meta",
bundle -> {
Procedure procedure = (Procedure) bundle.getEntry().get(0).getResource();
assertEquals(true, procedure.getMeta().isEmpty());
assertEquals("REASON_CODE", procedure.getReasonCode().get(0).getCoding().get(0).getCode());
assertEquals(0, procedure.getUsedCode().size());
DiagnosticReport dr = (DiagnosticReport) bundle.getEntry().get(1).getResource();
assertEquals(true, dr.getMeta().isEmpty());
Observation obs = (Observation ) bundle.getEntry().get(2).getResource();
assertEquals(true, obs.getMeta().isEmpty());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals(0, obs.getCode().getCoding().size());
assertEquals("STRING VALUE", obs.getValueStringType().getValue());
});
}
@Test
public void testElementsFilterWithComplexPath() throws IOException {
createProcedureWithLongChain();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_elements=Procedure.reasonCode.coding.code",
bundle -> {
Procedure procedure = (Procedure) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", procedure.getMeta().getTag().get(0).getCode());
assertEquals("REASON_CODE", procedure.getReasonCode().get(0).getCoding().get(0).getCode());
assertEquals(null, procedure.getReasonCode().get(0).getCoding().get(0).getSystem());
assertEquals(null, procedure.getReasonCode().get(0).getCoding().get(0).getDisplay());
assertEquals(0, procedure.getUsedCode().size());
});
}
private void createProcedureWithLongChain() {
ourNextProcedure = new Procedure();
ourNextProcedure.setId("Procedure/PROC");
ourNextProcedure.addReasonCode().addCoding().setCode("REASON_CODE").setSystem("REASON_SYSTEM").setDisplay("REASON_DISPLAY");
ourNextProcedure.addUsedCode().addCoding().setCode("USED_CODE");
DiagnosticReport dr = new DiagnosticReport();
dr.setId("DiagnosticReport/DRA");
ourNextProcedure.addReport().setResource(dr);
Observation obs = new Observation();
obs.setId("Observation/OBSA");
obs.setStatus(Observation.ObservationStatus.FINAL);
obs.setSubject(new Reference("Patient/123"));
obs.getCode().addCoding().setSystem("http://loinc.org").setCode("1234-5");
obs.setValue(new StringType("STRING VALUE"));
dr.addResult().setResource(obs);
}
private void verifyXmlAndJson(String theUri, Consumer<Bundle> theVerifier) throws IOException {
verifyXmlAndJson(theUri, Bundle.class, theVerifier);
}
private <T extends IBaseResource> void verifyXmlAndJson(String theUri, Class<T> theType, Consumer<T> theVerifier) throws IOException {
EncodingEnum encodingEnum;
HttpGet httpGet;
encodingEnum = EncodingEnum.JSON;
httpGet = new HttpGet(theUri + "&_pretty=true&_format=" + encodingEnum.getFormatContentType());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
T response = encodingEnum.newParser(ourCtx).parseResource(theType, responseContent);
theVerifier.accept(response);
}
encodingEnum = EncodingEnum.XML;
httpGet = new HttpGet(theUri + "&_pretty=true&_format=" + encodingEnum.getFormatContentType());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
T response = encodingEnum.newParser(ourCtx).parseResource(theType, responseContent);
theVerifier.accept(response);
}
}
public static class DummyProcedureResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Procedure.class;
}
@Search
public Procedure search(@IncludeParam(allow = {"*"}) Collection<Include> theIncludes) {
return ourNextProcedure;
}
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Read
public Patient read(@IdParam IdType theId, @Elements Set<String> theElements) {
ourLastElements = theElements;
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
patient.getText().getDiv().setValueAsString("<div>THE DIV</div>");
patient.addName().setFamily("FAMILY");
patient.getMaritalStatus().addCoding().setCode("D");
return patient;
}
@Search()
public Patient search(@Elements Set<String> theElements) {
ourLastElements = theElements;
Patient patient = new Patient();
patient.setId("Patient/1/_history/1");
patient.getText().getDiv().setValueAsString("<div>THE DIV</div>");
patient.addName().setFamily("FAMILY");
patient.getMaritalStatus().addCoding().setCode("D");
return patient;
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.registerProvider(new DummyPatientResourceProvider());
ourServlet.registerProvider(new DummyProcedureResourceProvider());
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
}

View File

@ -89,36 +89,39 @@ public class SearchR4Test {
String linkSelf; String linkSelf;
// Initial search // Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active"))); assertThat(toJson(bundle), not(containsString("\"active\"")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl(); linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
assertThat(linkSelf, containsString("_elements=name")); assertThat(linkSelf, containsString("_elements=name"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name")); assertThat(linkNext, containsString("_elements=meta,name"));
ourLog.info(toJson(bundle)); ourLog.info(toJson(bundle));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active"))); assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name")); assertThat(linkNext, containsString("_elements=meta,name"));
assertThat(linkNext, containsString("_elements:exclude=active,birthDate"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active"))); assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name")); assertThat(linkNext, containsString("_elements=meta,name"));
assertThat(linkNext, containsString("_elements:exclude=active,birthDate"));
// Fetch the next page // Fetch the next page
httpGet = new HttpGet(linkNext); httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active"))); assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name")); assertThat(linkNext, containsString("_elements=meta,name"));
assertThat(linkNext, containsString("_elements:exclude=active,birthDate"));
} }

View File

@ -5,11 +5,13 @@ import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
@ -18,18 +20,17 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -37,11 +38,11 @@ import static org.junit.Assert.assertThat;
public class SummaryParamR4Test { public class SummaryParamR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class);
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4(); private static FhirContext ourCtx = FhirContext.forR4();
private static SummaryEnum ourLastSummary; private static SummaryEnum ourLastSummary;
private static List<SummaryEnum> ourLastSummaryList; private static List<SummaryEnum> ourLastSummaryList;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class);
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
@ -51,16 +52,14 @@ public class SummaryParamR4Test {
ourLastSummary = null; ourLastSummary = null;
ourLastSummaryList = null; ourLastSummaryList = null;
} }
@Test @Test
public void testReadSummaryData() throws Exception { public void testReadSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.DATA.getCode()); verifyXmlAndJson(
HttpResponse status = ourClient.execute(httpGet); "http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.DATA.getCode(),
String responseContent = IOUtils.toString(status.getEntity().getContent()); Patient.class,
IOUtils.closeQuietly(status.getEntity().getContent()); patient -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(Constants.CT_FHIR_XML_NEW + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
assertThat(responseContent, not(containsString("<Bundle"))); assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien"))); assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>"))); assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
@ -68,15 +67,15 @@ public class SummaryParamR4Test {
assertThat(responseContent, (containsString("maritalStatus"))); assertThat(responseContent, (containsString("maritalStatus")));
assertEquals(SummaryEnum.DATA, ourLastSummary); assertEquals(SummaryEnum.DATA, ourLastSummary);
} }
);
}
@Test @Test
public void testReadSummaryText() throws Exception { public void testReadSummaryText() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TEXT.getCode()); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TEXT.getCode());
HttpResponse status = ourClient.execute(httpGet); try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent()); String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -87,13 +86,13 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("efer"))); assertThat(responseContent, not(containsString("efer")));
assertEquals(SummaryEnum.TEXT, ourLastSummary); assertEquals(SummaryEnum.TEXT, ourLastSummary);
} }
}
@Test @Test
public void testReadSummaryTextWithMandatory() throws Exception { public void testReadSummaryTextWithMandatory() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest/1?_summary=" + SummaryEnum.TEXT.getCode()); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest/1?_summary=" + SummaryEnum.TEXT.getCode());
HttpResponse status = ourClient.execute(httpGet); try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent()); String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -104,17 +103,16 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("family"))); assertThat(responseContent, not(containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
} }
}
@Test @Test
public void testReadSummaryTrue() throws Exception { public void testReadSummaryTrue() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TRUE.getCode()); String url = "http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TRUE.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); Patient.class,
ourLog.info(responseContent); patient -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(Constants.CT_FHIR_XML_NEW + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
assertThat(responseContent, not(containsString("<Bundle"))); assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien"))); assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>"))); assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
@ -122,16 +120,17 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TRUE, ourLastSummary); assertEquals(SummaryEnum.TRUE, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryCount() throws Exception { public void testSearchSummaryCount() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>"))); assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, not(containsString("entry"))); assertThat(responseContent, not(containsString("entry")));
assertThat(responseContent, not(containsString("THE DIV"))); assertThat(responseContent, not(containsString("THE DIV")));
@ -139,64 +138,65 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.COUNT, ourLastSummary); assertEquals(SummaryEnum.COUNT, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryCountAndData() throws Exception { public void testSearchSummaryCountAndData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode() + "," + SummaryEnum.DATA.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode() + "," + SummaryEnum.DATA.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>"))); assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, not(containsString("THE DIV"))); assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, (containsString("family"))); assertThat(responseContent, (containsString("family")));
assertThat(responseContent, (containsString("maritalStatus"))); assertThat(responseContent, (containsString("maritalStatus")));
} }
);
}
@Test @Test
public void testSearchSummaryData() throws Exception { public void testSearchSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.DATA.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.DATA.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<Patient")); assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV"))); assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family")); assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus")); assertThat(responseContent, containsString("maritalStatus"));
assertEquals(SummaryEnum.DATA, ourLastSummary); assertEquals(SummaryEnum.DATA, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryFalse() throws Exception { public void testSearchSummaryFalse() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=false"); String url = "http://localhost:" + ourPort + "/Patient?_summary=false";
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<Patient")); assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, containsString("THE DIV")); assertThat(responseContent, containsString("THE DIV"));
assertThat(responseContent, containsString("family")); assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus")); assertThat(responseContent, containsString("maritalStatus"));
assertEquals(SummaryEnum.FALSE, ourLastSummary); assertEquals(SummaryEnum.FALSE, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryText() throws Exception { public void testSearchSummaryText() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TEXT.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TEXT.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>"))); assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString("THE DIV"))); assertThat(responseContent, (containsString("THE DIV")));
@ -204,32 +204,34 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TEXT, ourLastSummary); assertEquals(SummaryEnum.TEXT, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryTextWithMandatory() throws Exception { public void testSearchSummaryTextWithMandatory() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest?_summary=" + SummaryEnum.TEXT.getCode() + "&_pretty=true"); String url = "http://localhost:" + ourPort + "/MedicationRequest?_summary=" + SummaryEnum.TEXT.getCode() + "&_pretty=true";
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); assertEquals(0, bundle.getMeta().getTag().size());
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>"))); assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString(">TEXT<"))); assertThat(responseContent, (containsString(">TEXT<")));
assertThat(responseContent, (containsString("Medication/123"))); assertThat(responseContent, (containsString("Medication/123")));
assertThat(responseContent, not(containsStringIgnoringCase("note"))); assertThat(responseContent, not(containsStringIgnoringCase("note")));
} }
);
}
@Test @Test
public void testSearchSummaryTextMulti() throws Exception { public void testSearchSummaryTextMulti() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=multi&_summary=" + SummaryEnum.TEXT.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_query=multi&_summary=" + SummaryEnum.TEXT.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, (containsString("<total value=\"1\"/>"))); assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString("THE DIV"))); assertThat(responseContent, (containsString("THE DIV")));
@ -237,60 +239,63 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastSummaryList, contains(SummaryEnum.TEXT)); assertThat(ourLastSummaryList, contains(SummaryEnum.TEXT));
} }
);
}
@Test @Test
public void testSearchSummaryTrue() throws Exception { public void testSearchSummaryTrue() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TRUE.getCode()); String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TRUE.getCode();
HttpResponse status = ourClient.execute(httpGet); verifyXmlAndJson(
String responseContent = IOUtils.toString(status.getEntity().getContent()); url,
IOUtils.closeQuietly(status.getEntity().getContent()); bundle -> {
ourLog.info(responseContent); String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<Patient")); assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV"))); assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family")); assertThat(responseContent, containsString("family"));
assertThat(responseContent, not(containsString("maritalStatus"))); assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TRUE, ourLastSummary); assertEquals(SummaryEnum.TRUE, ourLastSummary);
} }
);
}
@Test @Test
public void testSearchSummaryWithTextAndOthers() throws Exception { public void testSearchSummaryWithTextAndOthers() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=text&_summary=data"); String url = "http://localhost:" + ourPort + "/Patient?_summary=text&_summary=data";
HttpResponse status = ourClient.execute(httpGet); try (CloseableHttpResponse status = ourClient.execute(new HttpGet(url))) {
String responseContent = IOUtils.toString(status.getEntity().getContent()); String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode()); assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("Can not combine _summary=text with other values for _summary")); assertThat(responseContent, containsString("Can not combine _summary=text with other values for _summary"));
} }
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
} }
@BeforeClass private void verifyXmlAndJson(String theUri, Consumer<Bundle> theVerifier) throws IOException {
public static void beforeClass() throws Exception { verifyXmlAndJson(theUri, Bundle.class, theVerifier);
ourPort = PortUtil.findFreePort(); }
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler(); private <T extends IBaseResource> void verifyXmlAndJson(String theUri, Class<T> theType, Consumer<T> theVerifier) throws IOException {
RestfulServer servlet = new RestfulServer(ourCtx); EncodingEnum encodingEnum;
HttpGet httpGet;
servlet.setResourceProviders(new DummyPatientResourceProvider(), new DummyMedicationRequestProvider()); encodingEnum = EncodingEnum.JSON;
ServletHolder servletHolder = new ServletHolder(servlet); httpGet = new HttpGet(theUri + "&_pretty=true&_format=" + encodingEnum.getFormatContentType());
proxyHandler.addServletWithMapping(servletHolder, "/*"); try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
ourServer.setHandler(proxyHandler); String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourServer.start(); ourLog.info(responseContent);
T response = encodingEnum.newParser(ourCtx).parseResource(theType, responseContent);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); theVerifier.accept(response);
HttpClientBuilder builder = HttpClientBuilder.create(); }
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
encodingEnum = EncodingEnum.XML;
httpGet = new HttpGet(theUri + "&_pretty=true&_format=" + encodingEnum.getFormatContentType());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
T response = encodingEnum.newParser(ourCtx).parseResource(theType, responseContent);
theVerifier.accept(response);
}
} }
public static class DummyMedicationRequestProvider implements IResourceProvider { public static class DummyMedicationRequestProvider implements IResourceProvider {
@ -312,7 +317,7 @@ public class SummaryParamR4Test {
@Search @Search
public List<MedicationRequest> read() { public List<MedicationRequest> read() {
return Arrays.asList(read(new IdType("999"))); return Collections.singletonList(read(new IdType("999")));
} }
} }
@ -359,4 +364,31 @@ public class SummaryParamR4Test {
} }
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(new DummyPatientResourceProvider(), new DummyMedicationRequestProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
} }

View File

@ -940,7 +940,6 @@ public class JsonParserDstu2_1Test {
assertThat(out, containsString("name")); assertThat(out, containsString("name"));
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
} }
} }

View File

@ -1568,7 +1568,6 @@ public class XmlParserDstu2_1Test {
assertThat(out, containsString("name")); assertThat(out, containsString("name"));
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
} }
} }
@ -1597,7 +1596,6 @@ public class XmlParserDstu2_1Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1609,7 +1607,6 @@ public class XmlParserDstu2_1Test {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);

View File

@ -1143,7 +1143,6 @@ public class Dstu3JsonParserTest {
assertThat(out, containsString("name")); assertThat(out, containsString("name"));
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
} }
} }

View File

@ -1938,7 +1938,6 @@ public class Dstu3XmlParserTest {
assertThat(out, containsString("name")); assertThat(out, containsString("name"));
assertThat(out, containsString("id")); assertThat(out, containsString("id"));
assertThat(out, not(containsString("address"))); assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
} }
} }
@ -1967,7 +1966,6 @@ public class Dstu3XmlParserTest {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);
@ -1979,7 +1977,6 @@ public class Dstu3XmlParserTest {
{ {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient"))); p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle); String out = p.encodeResourceToString(bundle);
ourLog.info(out); ourLog.info(out);

View File

@ -151,57 +151,6 @@ public class R4JsonParserTest {
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
} }
@Test
public void testExcludeRootStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("id");
excludes.add("meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, (containsString("PATIENTID")));
assertThat(encoded, (containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testExcludeStarDotStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("*.id");
excludes.add("*.meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, not(containsString("PATIENTID")));
assertThat(encoded, not(containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertNotEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
/** /**
* Test that long JSON strings don't get broken up * Test that long JSON strings don't get broken up

View File

@ -106,32 +106,6 @@ public class R4XmlParserTest {
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString()); assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
} }
@Test
public void testExcludeRootStuff() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("id");
excludes.add("meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, IsNot.not(containsString("BUNDLEID")));
assertThat(encoded, IsNot.not(containsString("http://FOO")));
assertThat(encoded, (containsString("PATIENTID")));
assertThat(encoded, (containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test @Test
public void testExcludeStarDotStuff() { public void testExcludeStarDotStuff() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);

View File

@ -689,6 +689,55 @@
</section> </section>
<section name="Extended Elements Support" id="extended_elements_support">
<p>
The HAPI FHIR server may be configured using the
<code>RestfulServer#setElementsSupport</code> to enable extended
support for the <code>_elements</code> filter.
</p>
<p>
In standard mode, elements are supported exactly as described in the
<a href="http://hl7.org/fhir/search.html#elements">Elements Documentation</a>
in the FHIR specification.
</p>
<p>
In extended mode, HAPI FHIR provides the same behaviour as described in the
FHIR specification, but also enabled the following additional options:
</p>
<ul>
<li>
Values may be prepended using Resource names in order to apply the
elements filter to multiple resources. For example, the following
parameter could be used to apply elements filtering to both the
DiagnosticReport and Observation resource in a search result:<br/>
<code>_elements=DiagnosticReport.subject,DiagnosticReport.result,Observation.value</code>
</li>
<li>
Values may be prepended with a wildcard star in order to apply them
to all resource types. For example, the following parameter could
be used to include the <code>subject</code> field in all resource
types:<br/>
<code>_elements=*.subject</code>
</li>
<li>
Values may include complex paths. For example, the following parameter could
be used to include only the code on a coded element:<br/>
<code>_elements=Procedure.reasonCode.coding.code</code>
</li>
<li>
Elements may be excluded using the <code>:exclude</code> modifier
on the elements parameter. For example, the following parameter
could be used to remove the resource metadata (meta) element from
all resources in the response:<br/>
<code>_elements:exclude=*.meta</code><br/>
Note that this can be used to suppress the <code>SUBSETTED</code> tag
which is automatically added to resources when an <code>_elements</code>
parameter is applied.
</li>
</ul>
</section>
</body> </body>
</document> </document>