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 org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.*;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -47,11 +49,9 @@ public abstract class BaseParser implements IParser {
private ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private FhirContext myContext;
private Set<String> myDontEncodeElements;
private boolean myDontEncodeElementsIncludesStars;
private Set<String> myEncodeElements;
private List<ElementsPath> myDontEncodeElements;
private List<ElementsPath> myEncodeElements;
private Set<String> myEncodeElementsAppliesToResourceTypes;
private boolean myEncodeElementsIncludesStars;
private IIdType myEncodeForceResourceId;
private IParserErrorHandler myErrorHandler;
private boolean myOmitResourceId;
@ -65,20 +65,19 @@ public abstract class BaseParser implements IParser {
/**
* Constructor
*
* @param theParserErrorHandler
*/
public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
myContext = theContext;
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());
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
return new Iterable<BaseParser.CompositeChildElement>() {
@Override
public Iterator<CompositeChildElement> iterator() {
@ -106,7 +105,7 @@ public abstract class BaseParser implements IParser {
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
@ -181,8 +180,6 @@ public abstract class BaseParser implements IParser {
theContained.getExistingIdToContainedResource().put(nextId, next);
}
}
} else {
// no resources to contain
}
List<IBaseReference> allReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
@ -279,7 +276,7 @@ public abstract class BaseParser implements IParser {
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;
@ -296,15 +293,27 @@ public abstract class BaseParser implements IParser {
@Override
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(theWriter, "theWriter can not be null");
Validate.notNull(theEncodeContext, "theEncodeContext can not be null");
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
throw new IllegalArgumentException(
"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) {
@ -392,43 +401,32 @@ public abstract class BaseParser implements IParser {
return myDontStripVersionsFromReferencesAtPaths;
}
/**
* See {@link #setEncodeElements(Set)}
*/
@Override
public Set<String> getEncodeElements() {
return myEncodeElements;
}
@Override
public void setEncodeElements(Set<String> theEncodeElements) {
myEncodeElementsIncludesStars = false;
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
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;
} 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) {
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()) {
if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) {
extensionMetadataKeys.add(entry);
@ -466,9 +464,9 @@ public abstract class BaseParser implements IParser {
return url;
}
protected TagList getMetaTagsForEncoding(IResource theIResource) {
protected TagList getMetaTagsForEncoding(IResource theIResource, EncodeContext theEncodeContext) {
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
if (shouldAddSubsettedTag()) {
if (shouldAddSubsettedTag(theEncodeContext)) {
tags = new TagList(tags);
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,
CompositeChildElement theCompositeChildElement) {
CompositeChildElement theCompositeChildElement, EncodeContext theEncodeContext) {
if (myContext.getVersion().getVersion().isRi()) {
/*
@ -754,7 +752,7 @@ public abstract class BaseParser implements IParser {
}
}
if (shouldAddSubsettedTag()) {
if (shouldAddSubsettedTag(theEncodeContext)) {
IBaseCoding coding = metaValue.addTag();
coding.setCode(Constants.TAG_SUBSETTED_CODE);
coding.setSystem(getSubsettedCodeSystem());
@ -806,16 +804,13 @@ public abstract class BaseParser implements IParser {
@Override
public void setDontEncodeElements(Set<String> theDontEncodeElements) {
myDontEncodeElementsIncludesStars = false;
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
myDontEncodeElements = null;
} else {
myDontEncodeElements = theDontEncodeElements;
for (String next : theDontEncodeElements) {
if (next.startsWith("*.")) {
myDontEncodeElementsIncludesStars = true;
}
}
myDontEncodeElements = theDontEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
}
}
@ -885,22 +880,39 @@ public abstract class BaseParser implements IParser {
return this;
}
protected boolean shouldAddSubsettedTag() {
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
protected boolean shouldAddSubsettedTag(EncodeContext theEncodeContext) {
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;
if (isOmitResourceId()) {
retVal = false;
} else {
if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName();
if (myDontEncodeElements.contains(resourceName + ".id")) {
if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath(resourceName + ".id"))) {
retVal = false;
} else if (myDontEncodeElements.contains("*.id")) {
} else if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("*.id"))) {
retVal = false;
} else if (theSubResource == false && myDontEncodeElements.contains("id")) {
} else if (theEncodeContext.getResourcePath().size() == 1 && myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("id"))) {
retVal = false;
}
}
@ -914,9 +926,11 @@ public abstract class BaseParser implements IParser {
protected boolean shouldEncodeResourceMeta(IResource theResource) {
if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName();
if (myDontEncodeElements.contains(resourceName + ".meta")) {
if (myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath(resourceName + ".meta"))) {
return false;
} else return !myDontEncodeElements.contains("*.meta");
} else {
return myDontEncodeElements.stream().anyMatch(t -> t.startsWithPath("*.meta"));
}
}
return true;
}
@ -965,13 +979,13 @@ public abstract class BaseParser implements IParser {
private final BaseRuntimeChildDefinition myDef;
private final CompositeChildElement myParent;
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;
myParent = theParent;
myResDef = null;
mySubResource = theSubResource;
myEncodeContext = theEncodeContext;
if (ourLog.isTraceEnabled()) {
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;
myDef = null;
myParent = null;
mySubResource = theSubResource;
myEncodeContext = theEncodeContext;
}
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
@ -1038,71 +1052,75 @@ public abstract class BaseParser implements IParser {
}
}
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
Set<String> encodeElements = myEncodeElements;
if (encodeElements != null && encodeElements.isEmpty() == false) {
if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) {
private boolean checkIfParentShouldBeEncodedAndBuildPath() {
List<ElementsPath> encodeElements = myEncodeElements;
String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName();
if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
encodeElements = null;
}
}
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
}
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
}
boolean retVal = checkIfPathMatchesForEncoding(encodeElements, true);
private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) {
if (myResDef != null) {
if (theResourceTypes != null) {
if (!theResourceTypes.contains(myResDef.getName())) {
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('.');
/*
* We force the meta tag to be encoded even if it's not specified as an element in the
* elements filter, specifically because we'll need it in order to automatically add
* the SUBSETTED tag
*/
if (!retVal) {
if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) {
if (!myParent.isSubResource()) {
return true;
}
if ("meta".equals(myEncodeContext.getLeafResourcePathFirstField()) && shouldAddSubsettedTag(myEncodeContext)) {
// The next element is a child of the <meta> element
retVal = true;
} else if ("meta".equals(myDef.getElementName()) && shouldAddSubsettedTag(myEncodeContext)) {
// The next element is the <meta> element
retVal = true;
}
}
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() {
@ -1113,38 +1131,189 @@ public abstract class BaseParser implements IParser {
return myParent;
}
public RuntimeResourceDefinition getResDef() {
return myResDef;
}
private boolean isSubResource() {
return mySubResource;
}
public boolean shouldBeEncoded() {
boolean retVal = true;
if (myEncodeElements != null) {
retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false);
if (retVal == false && myEncodeElementsIncludesStars) {
retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true);
}
retVal = checkIfParentShouldBeEncodedAndBuildPath();
}
if (retVal && myDontEncodeElements != null) {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), false);
if (retVal && myDontEncodeElementsIncludesStars) {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
}
}
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
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 {
private long myNextContainedId = 1;
@ -1242,7 +1411,6 @@ public abstract class BaseParser implements IParser {
for (IBaseResource nextResource : getResourceList()) {
if (getResourceToIdMap().get(nextResource) != null) {
ids.add(getResourceToIdMap().get(nextResource).getValue());
continue;
}
}
@ -1274,24 +1442,24 @@ public abstract class BaseParser implements IParser {
return new ArrayList<>(securityLabels);
}
static boolean hasExtensions(IBase theElement) {
static boolean hasNoExtensions(IBase theElement) {
if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
return true;
return false;
}
}
if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
if (res.hasExtension()) {
return true;
return false;
}
}
if (theElement instanceof IBaseHasModifierExtensions) {
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;
/**
* 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
* resources encoded by this parser
@ -258,14 +248,6 @@ public interface IParser {
*/
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
*/

View File

@ -140,21 +140,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
return retVal;
}
public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException {
public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
if (myPrettyPrint) {
theEventWriter.setPrettyPrint(myPrettyPrint);
}
theEventWriter.init();
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, false);
encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext);
theEventWriter.flush();
}
@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);
doEncodeResourceToJsonLikeWriter(theResource, eventWriter);
doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext);
}
@Override
@ -192,8 +192,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem,
boolean theForceEmpty) throws IOException {
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
boolean theForceEmpty, EncodeContext theEncodeContext) throws IOException {
switch (theChildDef.getChildType()) {
case ID_DATATYPE: {
@ -265,7 +265,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else {
theEventWriter.beginObject();
}
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem, theEncodeContext);
theEventWriter.endObject();
break;
}
@ -283,7 +283,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
for (IBaseResource next : containedResources) {
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();
@ -311,7 +311,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
case RESOURCE:
IBaseResource resource = (IBaseResource) theNextValue;
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;
case UNDECL_EXT:
default:
@ -321,7 +325,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
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);
@ -331,14 +335,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
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();
if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
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;
}
continue;
@ -361,7 +365,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
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;
}
}
@ -369,12 +373,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else if (nextChild instanceof RuntimeChildContainedResources) {
String childName = nextChild.getValidChildNames().iterator().next();
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;
}
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()) {
continue;
@ -407,6 +411,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
String childName = childNameAndDef.getChildName();
theEncodeContext.pushPath(childName, false);
BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
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) {
beginArray(theEventWriter, childName);
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) {
// suppress narratives from contained resources
} 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;
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
}
valueIdx++;
theEncodeContext.popPath();
}
if (inArray) {
@ -525,7 +532,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
theEventWriter.endArray();
}
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext);
if (inArray) {
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,
CompositeChildElement theParent) throws IOException, DataFormatException {
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException {
writeCommentsPreAndPost(theNextValue, theEventWriter);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext);
}
@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());
}
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,
boolean theContainedResource, boolean theSubResource) throws IOException {
boolean theContainedResource, EncodeContext theEncodeContext) throws IOException {
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())) {
resourceId = theResource.getIdElement();
@ -588,18 +582,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
if (!theContainedResource) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) {
resourceId = null;
} else if (!theSubResource && getEncodeForceResourceId() != null) {
} else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
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,
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException {
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}
@ -630,7 +624,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
writeCommentsPreAndPost(theResourceId, theEventWriter);
}
if (haveExtension) {
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
}
theEventWriter.endObject();
}
@ -644,7 +638,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
profiles = super.getProfileTagsForEncoding(resource, profiles);
TagList tags = getMetaTagsForEncoding(resource);
TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
IdDt resourceId = resource.getId();
String versionIdPart = resourceId.getVersionIdPart();
@ -672,7 +666,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
beginArray(theEventWriter, "security");
for (BaseCodingDt securityLabel : securityLabels) {
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.endArray();
@ -693,23 +689,23 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endArray();
}
addExtensionMetadata(theResDef, theResource, theContainedResource, theSubResource, extensionMetadataKeys, resDef, theEventWriter);
addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
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();
}
private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
boolean theContainedResource, boolean theSubResource,
boolean theContainedResource,
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
RuntimeResourceDefinition resDef,
JsonLikeWriter theEventWriter) throws IOException {
JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
if (extensionMetadataKeys.isEmpty()) {
return;
}
@ -718,7 +714,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
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
*/
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> modifierExtensions = new ArrayList<>(0);
@ -739,7 +735,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
// 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,
@ -1260,18 +1256,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
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) {
beginArray(theEventWriter, "extension");
for (HeldExtension next : extensions) {
next.write(resDef, theResource, theEventWriter);
next.write(resDef, theResource, theEventWriter, theEncodeContext);
}
theEventWriter.endArray();
}
if (modifierExtensions.isEmpty() == false) {
beginArray(theEventWriter, "modifierExtension");
for (HeldExtension next : modifierExtensions) {
next.write(resDef, theResource, theEventWriter);
next.write(resDef, theResource, theEventWriter, theEncodeContext);
}
theEventWriter.endArray();
}
@ -1334,7 +1330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
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)) {
final List<HeldExtension> extensions = 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) {
beginObject(theEventWriter, '_' + childName);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
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) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension);
writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext);
} else {
theEventWriter.beginObject();
@ -1372,7 +1368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
*
* 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
// // (grrrrrr....)
@ -1391,18 +1387,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext);
} else {
String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false);
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext);
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext);
}
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();
final String extensionUrl = getExtensionUrl(ext.getUrl());
@ -1429,7 +1425,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
for (Object next : ext.getExtension()) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next);
writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext);
}
theEventWriter.endArray();
} 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
* 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();
String childName = extDef.getChildNameByDatatype(value.getClass());
@ -1449,8 +1445,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (childDef == null) {
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);
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, myParent,false, theEncodeContext);
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext);
}
// theEventWriter.name(myUndeclaredExtension.get);

View File

@ -20,28 +20,32 @@ package ca.uhn.fhir.parser;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.*;
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.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.XhtmlDt;
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.stream.*;
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 org.hl7.fhir.instance.model.api.*;
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.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* 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 FHIR_NS = "http://hl7.org/fhir";
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_REFERENCE = "reference";
static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
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 FhirContext myContext;
private boolean myPrettyPrint;
@ -101,12 +104,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
@Override
public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException {
public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws DataFormatException {
XMLStreamWriter eventWriter;
try {
eventWriter = createXmlWriter(theWriter);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false, theEncodeContext);
eventWriter.flush();
} catch (XMLStreamException 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);
try {
List<String> heldComments = new ArrayList<String>(1);
List<String> heldComments = new ArrayList<>(1);
while (streamReader.hasNext()) {
XMLEvent nextEvent = streamReader.nextEvent();
@ -167,10 +170,8 @@ public class XmlParser extends BaseParser /* implements IParser */ {
heldComments.clear();
}
@SuppressWarnings("unchecked")
Iterator<Attribute> attributes = elem.getAttributes();
for (Iterator<Attribute> iter = attributes; iter.hasNext();) {
Attribute next = iter.next();
for (Iterator<Attribute> attributes = elem.getAttributes(); attributes.hasNext(); ) {
Attribute next = attributes.next();
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,
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 (isChildContained(childDef, theIncludedResource)) {
// We still want to go in..
@ -230,12 +234,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case ID_DATATYPE: {
IIdType value = IIdType.class.cast(theElement);
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);
if (StringUtils.isNotBlank(encodedValue)) {
theEventWriter.writeAttribute("value", encodedValue);
}
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource);
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement();
}
break;
@ -243,7 +247,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case PRIMITIVE_DATATYPE: {
IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
String value = pd.getValueAsString();
if (value != null || super.hasExtensions(pd)) {
if (value != null || !super.hasNoExtensions(pd)) {
theEventWriter.writeStartElement(childName);
String elementId = getCompositeElementId(theElement);
if (isNotBlank(elementId)) {
@ -252,7 +256,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (value != null) {
theEventWriter.writeAttribute("value", value);
}
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource);
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement();
}
break;
@ -267,7 +271,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (isNotBlank(theExtensionUrl)) {
theEventWriter.writeAttribute("url", theExtensionUrl);
}
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent);
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent, theEncodeContext);
theEventWriter.writeEndElement();
break;
}
@ -281,7 +285,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue()));
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
theEventWriter.writeEndElement();
}
break;
@ -289,7 +293,10 @@ public class XmlParser extends BaseParser /* implements IParser */ {
case RESOURCE: {
theEventWriter.writeStartElement(childName);
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();
break;
}
@ -318,12 +325,16 @@ public class XmlParser extends BaseParser /* implements IParser */ {
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 {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
@ -353,17 +364,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
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;
}
}
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 {
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()) {
continue;
@ -383,7 +394,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
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) {
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
if ((extension.getValue() == null || extension.getValue().isEmpty())) {
@ -391,18 +402,19 @@ public class XmlParser extends BaseParser /* implements IParser */ {
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) {
// suppress narratives from contained resources
} 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 {
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
if (extDef.isModifier()) {
@ -417,27 +429,27 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
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();
}
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) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theEncodeContext);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource, theEncodeContext);
}
if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource);
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext);
}
if (theElement instanceof IBaseHasModifierExtensions) {
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;
if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
@ -448,17 +460,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
if (!theIncludedResource) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) {
resourceId = null;
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
} else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
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) {
super.containResourcesForEncoding(theResource);
}
@ -477,12 +489,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPre(theEventWriter, theResourceId);
theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false);
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId);
}
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
} else {
@ -495,7 +507,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPost(theEventWriter, theResourceId);*/
theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false);
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId);
}
@ -510,7 +522,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.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) {
theEventWriter.writeStartElement("meta");
@ -526,7 +538,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.writeStartElement("security");
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
theEventWriter.writeEndElement();
}
if (tags != null) {
@ -549,7 +561,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
} 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();
}
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 {
for (IBaseExtension<?, ?> next : theExtensions) {
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());
}
}
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null);
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null, theEncodeContext);
}
// child extensions
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource);
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext);
theEventWriter.writeEndElement();

View File

@ -137,6 +137,7 @@ public class Constants {
public static final String PARAM_COUNT = "_count";
public static final String PARAM_DELETE = "_delete";
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_HAS = "_has";
public static final String PARAM_HISTORY = "_history";

View File

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

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.util;
* #L%
*/
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -55,6 +56,24 @@ public class PortUtil {
server = new ServerSocket(0);
server.setReuseAddress(true);
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();
/*

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

View File

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

View File

@ -151,11 +151,6 @@ public class JaxRsPatientRestProviderDstu3 extends AbstractJaxRsResourceProvider
return true;
}
@Override
public boolean isUseBrowserFriendlyContentTypes() {
return true;
}
@GET
@Path("/{id}/$firstVersion")
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
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());
} 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();
if (count > 0) {
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")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
myDaoConfig.setMaximumSearchResultCountInTransaction(new DaoConfig().getMaximumSearchResultCountInTransaction());
}

View File

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

View File

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

View File

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

View File

@ -51,7 +51,6 @@ public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test {
@SuppressWarnings("deprecation")
@After
public void after() {
myRestServer.setUseBrowserFriendlyContentTypes(true);
ourClient.unregisterInterceptor(mySimpleHeaderInterceptor);
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.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
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.*;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
@ -204,6 +201,11 @@ public class TestRestfulServer extends RestfulServer {
setDefaultPrettyPrint(true);
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
* 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();
/**
* @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
* providers should generally use this context if one is needed, as opposed to
@ -78,12 +85,6 @@ public interface IRestfulServerDefaults {
*/
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.interceptor.ExceptionHandlingInterceptor;
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.ConformanceMethodBinding;
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 boolean myStarted;
private boolean myUncompressIncomingContents = true;
private boolean myUseBrowserFriendlyContentTypes;
private ITenantIdentificationStrategy myTenantIdentificationStrategy;
private Date myConformanceDate;
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
@ -500,6 +499,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
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
* {@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_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.
@ -1251,26 +1266,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
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) {
UrlPathTokenizer tok = new UrlPathTokenizer(theRequestPath);

View File

@ -46,6 +46,7 @@ import java.io.Writer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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 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 enum NarrativeModeEnum {
@ -125,13 +126,15 @@ public class RestfulServerUtils {
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails);
// _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))) {
throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters");
}
Set<String> elementsAppliesTo = null;
if (elements != null && isNotBlank(theRequestDetails.getResourceName())) {
elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName());
// _elements:exclude
Set<String> elementsExclude = ElementsParameter.getElementsValueOrNull(theRequestDetails, true);
if (elementsExclude != null) {
parser.setDontEncodeElements(elementsExclude);
}
if (summaryMode != null) {
@ -139,15 +142,27 @@ public class RestfulServerUtils {
parser.setEncodeElements(Collections.singleton("Bundle.total"));
} else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
} else {
parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA));
parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE));
}
}
if (elements != null && elements.size() > 0) {
String elementsAppliesTo = "*";
if (isNotBlank(theRequestDetails.getResourceName())) {
elementsAppliesTo = theRequestDetails.getResourceName();
}
Set<String> newElements = new HashSet<>();
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
* (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;
for (String next : newElements) {
if (next.startsWith("Bundle.")) {
@ -172,6 +180,13 @@ public class RestfulServerUtils {
break;
}
}
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
if (!haveExplicitBundleElement) {
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
}
@ -181,26 +196,28 @@ public class RestfulServerUtils {
}
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) {
return createPagingLink(theIncludes, theServerBase, theSearchId, theOffset, theCount, theRequestParameters, thePrettyPrint,
return createPagingLink(theIncludes, theRequestDetails, theSearchId, theOffset, theCount, theRequestParameters, thePrettyPrint,
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) {
return createPagingLink(theIncludes, theServerBase, theSearchId, null, null, theRequestParameters, thePrettyPrint,
return createPagingLink(theIncludes, theRequestDetails, theSearchId, null, null, theRequestParameters, thePrettyPrint,
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) {
String serverBase = theRequestDetails.getFhirServerBase();
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append(serverBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
@ -258,16 +275,33 @@ public class RestfulServerUtils {
b.append(theBundleType.getCode());
}
String paramName = Constants.PARAM_ELEMENTS;
String[] params = theRequestParameters.get(paramName);
if (params != null) {
for (String nextValue : params) {
if (isNotBlank(nextValue)) {
// _elements
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails, false);
if (elements != null) {
b.append('&');
b.append(paramName);
b.append(Constants.PARAM_ELEMENTS);
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
searchId = theResult.getUuid();
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())) {
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) {
// We're doing offset pages
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) {
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.
* #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.rest.api.Constants;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
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.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 {
@ -42,7 +46,7 @@ public class ElementsParameter implements IParameter {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
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()) {
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
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
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, not(containsString("address")));
assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
}
}

View File

@ -1630,7 +1630,7 @@ public class XmlParserDstu2_1Test {
{
IParser p = ourCtx.newXmlParser();
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);
String out = p.encodeResourceToString(patient);
ourLog.info(out);
@ -1639,6 +1639,7 @@ public class XmlParserDstu2_1Test {
assertThat(out, containsString("id"));
assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
}
}
@ -1667,7 +1668,6 @@ public class XmlParserDstu2_1Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1679,7 +1679,6 @@ public class XmlParserDstu2_1Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -2701,12 +2700,6 @@ public class XmlParserDstu2_1Test {
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")
public static class TestPatientFor327 extends Patient {

View File

@ -1769,7 +1769,7 @@ public class XmlParserDstu2Test {
{
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);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1780,8 +1780,7 @@ public class XmlParserDstu2Test {
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1792,8 +1791,7 @@ public class XmlParserDstu2Test {
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElements(new HashSet<>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1818,7 +1816,7 @@ public class XmlParserDstu2Test {
{
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);
String out = p.encodeResourceToString(bundle);
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, not(containsString("address")));
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, not(containsString("address")));
assertThat(out, not(containsString("meta")));
assertThat(out, not(containsString("SUBSETTED")));
}
}
@ -1890,7 +1891,6 @@ public class XmlParserDstu3Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1902,7 +1902,6 @@ public class XmlParserDstu3Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);

View File

@ -8,6 +8,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -252,6 +253,7 @@ public class JsonParserR4Test {
}
@Test
@Ignore
public void testExcludeRootStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
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;
// 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);
assertThat(toJson(bundle), not(containsString("active")));
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
assertThat(linkSelf, containsString("_elements=name"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
assertThat(linkNext, containsString("_elements=meta,name"));
ourLog.info(toJson(bundle));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
assertThat(toJson(bundle), not(containsString("\"active\"")));
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
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
assertThat(toJson(bundle), not(containsString("\"active\"")));
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
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("active")));
assertThat(toJson(bundle), not(containsString("\"active\"")));
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.Search;
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.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
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.impl.client.CloseableHttpClient;
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.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
@ -37,11 +38,11 @@ import static org.junit.Assert.assertThat;
public class SummaryParamR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static SummaryEnum ourLastSummary;
private static List<SummaryEnum> ourLastSummaryList;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class);
private static int ourPort;
private static Server ourServer;
@ -51,16 +52,14 @@ public class SummaryParamR4Test {
ourLastSummary = null;
ourLastSummaryList = null;
}
@Test
public void testReadSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.DATA.getCode());
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_NEW + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.DATA.getCode(),
Patient.class,
patient -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
@ -68,15 +67,15 @@ public class SummaryParamR4Test {
assertThat(responseContent, (containsString("maritalStatus")));
assertEquals(SummaryEnum.DATA, ourLastSummary);
}
);
}
@Test
public void testReadSummaryText() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TEXT.getCode());
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -87,13 +86,13 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("efer")));
assertEquals(SummaryEnum.TEXT, ourLastSummary);
}
}
@Test
public void testReadSummaryTextWithMandatory() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest/1?_summary=" + SummaryEnum.TEXT.getCode());
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -104,17 +103,16 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("family")));
assertThat(responseContent, not(containsString("maritalStatus")));
}
}
@Test
public void testReadSummaryTrue() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TRUE.getCode());
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_NEW + Constants.CHARSET_UTF8_CTSUFFIX.replace(" ", "").toLowerCase(), status.getEntity().getContentType().getValue().replace(" ", "").replace("UTF", "utf"));
String url = "http://localhost:" + ourPort + "/Patient/1?_summary=" + SummaryEnum.TRUE.getCode();
verifyXmlAndJson(
url,
Patient.class,
patient -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(patient);
assertThat(responseContent, not(containsString("<Bundle")));
assertThat(responseContent, (containsString("<Patien")));
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
@ -122,16 +120,17 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TRUE, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryCount() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, not(containsString("entry")));
assertThat(responseContent, not(containsString("THE DIV")));
@ -139,64 +138,65 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.COUNT, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryCountAndData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode() + "," + SummaryEnum.DATA.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_pretty=true&_summary=" + SummaryEnum.COUNT.getCode() + "," + SummaryEnum.DATA.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, (containsString("family")));
assertThat(responseContent, (containsString("maritalStatus")));
}
);
}
@Test
public void testSearchSummaryData() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.DATA.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.DATA.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus"));
assertEquals(SummaryEnum.DATA, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryFalse() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=false");
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());
String url = "http://localhost:" + ourPort + "/Patient?_summary=false";
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, containsString("THE DIV"));
assertThat(responseContent, containsString("family"));
assertThat(responseContent, containsString("maritalStatus"));
assertEquals(SummaryEnum.FALSE, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryText() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TEXT.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TEXT.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString("THE DIV")));
@ -204,32 +204,34 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TEXT, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryTextWithMandatory() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MedicationRequest?_summary=" + SummaryEnum.TEXT.getCode() + "&_pretty=true");
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());
String url = "http://localhost:" + ourPort + "/MedicationRequest?_summary=" + SummaryEnum.TEXT.getCode() + "&_pretty=true";
verifyXmlAndJson(
url,
bundle -> {
assertEquals(0, bundle.getMeta().getTag().size());
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString(">TEXT<")));
assertThat(responseContent, (containsString("Medication/123")));
assertThat(responseContent, not(containsStringIgnoringCase("note")));
}
);
}
@Test
public void testSearchSummaryTextMulti() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=multi&_summary=" + SummaryEnum.TEXT.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_query=multi&_summary=" + SummaryEnum.TEXT.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString("THE DIV")));
@ -237,60 +239,63 @@ public class SummaryParamR4Test {
assertThat(responseContent, not(containsString("maritalStatus")));
assertThat(ourLastSummaryList, contains(SummaryEnum.TEXT));
}
);
}
@Test
public void testSearchSummaryTrue() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TRUE.getCode());
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());
String url = "http://localhost:" + ourPort + "/Patient?_summary=" + SummaryEnum.TRUE.getCode();
verifyXmlAndJson(
url,
bundle -> {
String responseContent = ourCtx.newXmlParser().encodeResourceToString(bundle);
assertThat(responseContent, containsString("<Patient"));
assertThat(responseContent, not(containsString("THE DIV")));
assertThat(responseContent, containsString("family"));
assertThat(responseContent, not(containsString("maritalStatus")));
assertEquals(SummaryEnum.TRUE, ourLastSummary);
}
);
}
@Test
public void testSearchSummaryWithTextAndOthers() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=text&_summary=data");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
String url = "http://localhost:" + ourPort + "/Patient?_summary=text&_summary=data";
try (CloseableHttpResponse status = ourClient.execute(new HttpGet(url))) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
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
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
private void verifyXmlAndJson(String theUri, Consumer<Bundle> theVerifier) throws IOException {
verifyXmlAndJson(theUri, Bundle.class, theVerifier);
}
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
private <T extends IBaseResource> void verifyXmlAndJson(String theUri, Class<T> theType, Consumer<T> theVerifier) throws IOException {
EncodingEnum encodingEnum;
HttpGet httpGet;
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();
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 DummyMedicationRequestProvider implements IResourceProvider {
@ -312,7 +317,7 @@ public class SummaryParamR4Test {
@Search
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("id"));
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("id"));
assertThat(out, not(containsString("address")));
assertThat(out, not(containsString("meta")));
}
}
@ -1597,7 +1596,6 @@ public class XmlParserDstu2_1Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
@ -1609,7 +1607,6 @@ public class XmlParserDstu2_1Test {
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);

View File

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

View File

@ -151,57 +151,6 @@ public class R4JsonParserTest {
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

View File

@ -106,32 +106,6 @@ public class R4XmlParserTest {
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
public void testExcludeStarDotStuff() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);

View File

@ -689,6 +689,55 @@
</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>
</document>