Configurable summary mode (#5944)
* Fix #5871 - Configurable summary mode * Add docs * Spotless * Add javadoc
This commit is contained in:
parent
ac3a5e2ad2
commit
ab0b62706a
|
@ -20,8 +20,10 @@
|
|||
package ca.uhn.fhir.context;
|
||||
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -42,6 +44,8 @@ public class ParserOptions {
|
|||
private Set<String> myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
|
||||
private boolean myOverrideResourceIdWithBundleEntryFullUrl = true;
|
||||
private boolean myAutoContainReferenceTargetsWithNoId = true;
|
||||
private Set<String> myEncodeElementsForSummaryMode = null;
|
||||
private Set<String> myDontEncodeElementsForSummaryMode = null;
|
||||
|
||||
/**
|
||||
* If set to {@literal true} (which is the default), contained resources may be specified by
|
||||
|
@ -143,7 +147,7 @@ public class ParserOptions {
|
|||
if (thePaths == null) {
|
||||
setDontStripVersionsFromReferencesAtPaths((List<String>) null);
|
||||
} else {
|
||||
setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
|
||||
setDontStripVersionsFromReferencesAtPaths(CollectionUtil.newSet(thePaths));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -205,4 +209,119 @@ public class ParserOptions {
|
|||
myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This option specifies one or more elements that should be included when the parser is encoding
|
||||
* a resource in {@link IParser#setSummaryMode(boolean) summary mode}, even if the element is not
|
||||
* a part of the base FHIR specification's list of summary elements. Examples of valid values
|
||||
* include:
|
||||
* <ul>
|
||||
* <li><b>Patient.maritalStatus</b> - Encode the entire maritalStatus CodeableConcept, even though Patient.maritalStatus is not a summary element</li>
|
||||
* <li><b>Patient.maritalStatus.text</b> - Encode only the text component of the patient's maritalStatus</li>
|
||||
* <li><b>*.text</b> - Encode the text element on any resource (only the very first position may contain a
|
||||
* wildcard)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see IParser#setSummaryMode(boolean)
|
||||
* @see IParser#setEncodeElements(Set) Can be used to specify these values for an individual parser instance.
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@SuppressWarnings({"UnusedReturnValue"})
|
||||
@Nonnull
|
||||
public ParserOptions setEncodeElementsForSummaryMode(@Nonnull String... theEncodeElements) {
|
||||
return setEncodeElementsForSummaryMode(CollectionUtil.newSet(theEncodeElements));
|
||||
}
|
||||
|
||||
/**
|
||||
* This option specifies one or more elements that should be included when the parser is encoding
|
||||
* a resource in {@link IParser#setSummaryMode(boolean) summary mode}, even if the element is not
|
||||
* a part of the base FHIR specification's list of summary elements. Examples of valid values
|
||||
* include:
|
||||
* <ul>
|
||||
* <li><b>Patient.maritalStatus</b> - Encode the entire maritalStatus CodeableConcept, even though Patient.maritalStatus is not a summary element</li>
|
||||
* <li><b>Patient.maritalStatus.text</b> - Encode only the text component of the patient's maritalStatus</li>
|
||||
* <li><b>*.text</b> - Encode the text element on any resource (only the very first position may contain a
|
||||
* wildcard)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see IParser#setSummaryMode(boolean)
|
||||
* @see IParser#setEncodeElements(Set) Can be used to specify these values for an individual parser instance.
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@Nonnull
|
||||
public ParserOptions setEncodeElementsForSummaryMode(@Nullable Collection<String> theEncodeElements) {
|
||||
Set<String> encodeElements = null;
|
||||
if (theEncodeElements != null && !theEncodeElements.isEmpty()) {
|
||||
encodeElements = new HashSet<>(theEncodeElements);
|
||||
}
|
||||
myEncodeElementsForSummaryMode = encodeElements;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the values provided to {@link #setEncodeElementsForSummaryMode(Collection)}
|
||||
* or <code>null</code>
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@Nullable
|
||||
public Set<String> getEncodeElementsForSummaryMode() {
|
||||
return myEncodeElementsForSummaryMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This option specifies one or more elements that should be excluded when the parser is encoding
|
||||
* a resource in {@link IParser#setSummaryMode(boolean) summary mode}, even if the element is
|
||||
* a part of the base FHIR specification's list of summary elements. Examples of valid values
|
||||
* include:
|
||||
* <ul>
|
||||
* <li><b>Patient.name</b> - Do not include the patient's name</li>
|
||||
* <li><b>Patient.name.family</b> - Do not include the patient's family name</li>
|
||||
* <li><b>*.name</b> - Do not include the name element on any resource type</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see IParser#setSummaryMode(boolean)
|
||||
* @see IParser#setDontEncodeElements(Collection) Can be used to specify these values for an individual parser instance.
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@SuppressWarnings({"UnusedReturnValue"})
|
||||
@Nonnull
|
||||
public ParserOptions setDontEncodeElementsForSummaryMode(@Nonnull String... theEncodeElements) {
|
||||
return setDontEncodeElementsForSummaryMode(CollectionUtil.newSet(theEncodeElements));
|
||||
}
|
||||
|
||||
/**
|
||||
* This option specifies one or more elements that should be excluded when the parser is encoding
|
||||
* a resource in {@link IParser#setSummaryMode(boolean) summary mode}, even if the element is
|
||||
* a part of the base FHIR specification's list of summary elements. Examples of valid values
|
||||
* include:
|
||||
* <ul>
|
||||
* <li><b>Patient.name</b> - Do not include the patient's name</li>
|
||||
* <li><b>Patient.name.family</b> - Do not include the patient's family name</li>
|
||||
* <li><b>*.name</b> - Do not include the name element on any resource type</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see IParser#setSummaryMode(boolean)
|
||||
* @see IParser#setDontEncodeElements(Collection) Can be used to specify these values for an individual parser instance.
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@Nonnull
|
||||
public ParserOptions setDontEncodeElementsForSummaryMode(@Nullable Collection<String> theDontEncodeElements) {
|
||||
Set<String> dontEncodeElements = null;
|
||||
if (theDontEncodeElements != null && !theDontEncodeElements.isEmpty()) {
|
||||
dontEncodeElements = new HashSet<>(theDontEncodeElements);
|
||||
}
|
||||
myDontEncodeElementsForSummaryMode = dontEncodeElements;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the values provided to {@link #setDontEncodeElementsForSummaryMode(Collection)}
|
||||
* or <code>null</code>
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@Nullable
|
||||
public Set<String> getDontEncodeElementsForSummaryMode() {
|
||||
return myDontEncodeElementsForSummaryMode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
|
|||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeChildContainedResources;
|
||||
import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
|
||||
|
@ -42,6 +43,7 @@ import ca.uhn.fhir.parser.path.EncodeContextPath;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
|
@ -106,9 +108,8 @@ public abstract class BaseParser implements IParser {
|
|||
private FhirTerser.ContainedResources myContainedResources;
|
||||
private boolean myEncodeElementsAppliesToChildResourcesOnly;
|
||||
private final FhirContext myContext;
|
||||
private List<EncodeContextPath> myDontEncodeElements;
|
||||
private List<EncodeContextPath> myEncodeElements;
|
||||
private Set<String> myEncodeElementsAppliesToResourceTypes;
|
||||
private Collection<String> myDontEncodeElements;
|
||||
private Collection<String> myEncodeElements;
|
||||
private IIdType myEncodeForceResourceId;
|
||||
private IParserErrorHandler myErrorHandler;
|
||||
private boolean myOmitResourceId;
|
||||
|
@ -131,52 +132,15 @@ public abstract class BaseParser implements IParser {
|
|||
return myContext;
|
||||
}
|
||||
|
||||
List<EncodeContextPath> getDontEncodeElements() {
|
||||
return myDontEncodeElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setDontEncodeElements(Collection<String> theDontEncodeElements) {
|
||||
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
|
||||
myDontEncodeElements = null;
|
||||
} else {
|
||||
myDontEncodeElements =
|
||||
theDontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
|
||||
}
|
||||
myDontEncodeElements = theDontEncodeElements;
|
||||
return this;
|
||||
}
|
||||
|
||||
List<EncodeContextPath> getEncodeElements() {
|
||||
return myEncodeElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setEncodeElements(Set<String> theEncodeElements) {
|
||||
|
||||
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
|
||||
myEncodeElements = null;
|
||||
myEncodeElementsAppliesToResourceTypes = null;
|
||||
} else {
|
||||
myEncodeElements =
|
||||
theEncodeElements.stream().map(EncodeContextPath::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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myEncodeElements = theEncodeElements;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -298,7 +262,7 @@ public abstract class BaseParser implements IParser {
|
|||
@Override
|
||||
public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter)
|
||||
throws IOException, DataFormatException {
|
||||
EncodeContext encodeContext = new EncodeContext();
|
||||
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
|
||||
encodeResourceToWriter(theResource, theWriter, encodeContext);
|
||||
}
|
||||
|
||||
|
@ -321,7 +285,7 @@ public abstract class BaseParser implements IParser {
|
|||
} else if (theElement instanceof IPrimitiveType) {
|
||||
theWriter.write(((IPrimitiveType<?>) theElement).getValueAsString());
|
||||
} else {
|
||||
EncodeContext encodeContext = new EncodeContext();
|
||||
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
|
||||
encodeToWriter(theElement, theWriter, encodeContext);
|
||||
}
|
||||
}
|
||||
|
@ -628,7 +592,7 @@ public abstract class BaseParser implements IParser {
|
|||
return false;
|
||||
}
|
||||
|
||||
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
|
||||
Set<String> dontStripVersionsFromReferencesAtPaths = getDontStripVersionsFromReferencesAtPaths();
|
||||
if (dontStripVersionsFromReferencesAtPaths != null
|
||||
&& !dontStripVersionsFromReferencesAtPaths.isEmpty()
|
||||
&& theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
|
||||
|
@ -923,7 +887,7 @@ public abstract class BaseParser implements IParser {
|
|||
if (isSuppressNarratives()) {
|
||||
return true;
|
||||
}
|
||||
if (myEncodeElements != null) {
|
||||
if (theEncodeContext.myEncodeElementPaths != null) {
|
||||
if (isEncodeElementsAppliesToChildResourcesOnly()
|
||||
&& theEncodeContext.getResourcePath().size() < 2) {
|
||||
return false;
|
||||
|
@ -933,8 +897,8 @@ public abstract class BaseParser implements IParser {
|
|||
.getResourcePath()
|
||||
.get(theEncodeContext.getResourcePath().size() - 1)
|
||||
.getName();
|
||||
return myEncodeElementsAppliesToResourceTypes == null
|
||||
|| myEncodeElementsAppliesToResourceTypes.contains(currentResourceName);
|
||||
return theEncodeContext.myEncodeElementsAppliesToResourceTypes == null
|
||||
|| theEncodeContext.myEncodeElementsAppliesToResourceTypes.contains(currentResourceName);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -945,14 +909,15 @@ public abstract class BaseParser implements IParser {
|
|||
if (isOmitResourceId() && theEncodeContext.getPath().size() == 1) {
|
||||
retVal = false;
|
||||
} else {
|
||||
if (myDontEncodeElements != null) {
|
||||
if (theEncodeContext.myDontEncodeElementPaths != null) {
|
||||
String resourceName = myContext.getResourceType(theResource);
|
||||
if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) {
|
||||
if (theEncodeContext.myDontEncodeElementPaths.stream()
|
||||
.anyMatch(t -> t.equalsPath(resourceName + ".id"))) {
|
||||
retVal = false;
|
||||
} else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) {
|
||||
} else if (theEncodeContext.myDontEncodeElementPaths.stream().anyMatch(t -> t.equalsPath("*.id"))) {
|
||||
retVal = false;
|
||||
} else if (theEncodeContext.getResourcePath().size() == 1
|
||||
&& myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("id"))) {
|
||||
&& theEncodeContext.myDontEncodeElementPaths.stream().anyMatch(t -> t.equalsPath("id"))) {
|
||||
retVal = false;
|
||||
}
|
||||
}
|
||||
|
@ -963,19 +928,22 @@ public abstract class BaseParser implements IParser {
|
|||
/**
|
||||
* Used for DSTU2 only
|
||||
*/
|
||||
protected boolean shouldEncodeResourceMeta(IResource theResource) {
|
||||
return shouldEncodePath(theResource, "meta");
|
||||
protected boolean shouldEncodeResourceMeta(IResource theResource, EncodeContext theEncodeContext) {
|
||||
return shouldEncodePath(theResource, "meta", theEncodeContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for DSTU2 only
|
||||
*/
|
||||
protected boolean shouldEncodePath(IResource theResource, String thePath) {
|
||||
if (myDontEncodeElements != null) {
|
||||
protected boolean shouldEncodePath(IResource theResource, String thePath, EncodeContext theEncodeContext) {
|
||||
if (theEncodeContext.myDontEncodeElementPaths != null) {
|
||||
String resourceName = myContext.getResourceType(theResource);
|
||||
if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) {
|
||||
if (theEncodeContext.myDontEncodeElementPaths.stream()
|
||||
.anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) {
|
||||
return false;
|
||||
} else return myDontEncodeElements.stream().noneMatch(t -> t.equalsPath("*." + thePath));
|
||||
} else {
|
||||
return theEncodeContext.myDontEncodeElementPaths.stream().noneMatch(t -> t.equalsPath("*." + thePath));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -994,16 +962,16 @@ public abstract class BaseParser implements IParser {
|
|||
b.append(" but this is not a valid type for this element");
|
||||
if (nextChild instanceof RuntimeChildChoiceDefinition) {
|
||||
RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild;
|
||||
b.append(" - Expected one of: " + choice.getValidChildTypes());
|
||||
b.append(" - Expected one of: ").append(choice.getValidChildTypes());
|
||||
}
|
||||
throw new DataFormatException(Msg.code(1831) + b);
|
||||
}
|
||||
throw new DataFormatException(Msg.code(1832) + nextChild + " has no child of type " + theType);
|
||||
}
|
||||
|
||||
protected boolean shouldEncodeResource(String theName) {
|
||||
if (myDontEncodeElements != null) {
|
||||
for (EncodeContextPath next : myDontEncodeElements) {
|
||||
protected boolean shouldEncodeResource(String theName, EncodeContext theEncodeContext) {
|
||||
if (theEncodeContext.myDontEncodeElementPaths != null) {
|
||||
for (EncodeContextPath next : theEncodeContext.myDontEncodeElementPaths) {
|
||||
if (next.equalsPath(theName)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1012,11 +980,6 @@ public abstract class BaseParser implements IParser {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected boolean isFhirVersionLessThanOrEqualTo(FhirVersionEnum theFhirVersionEnum) {
|
||||
final FhirVersionEnum apiFhirVersion = myContext.getVersion().getVersion();
|
||||
return theFhirVersionEnum == apiFhirVersion || apiFhirVersion.isOlderThan(theFhirVersionEnum);
|
||||
}
|
||||
|
||||
protected void containResourcesInReferences(IBaseResource theResource) {
|
||||
|
||||
/*
|
||||
|
@ -1043,7 +1006,7 @@ public abstract class BaseParser implements IParser {
|
|||
myContainedResources = getContext().newTerser().containResources(theResource);
|
||||
}
|
||||
|
||||
class ChildNameAndDef {
|
||||
static class ChildNameAndDef {
|
||||
|
||||
private final BaseRuntimeElementDefinition<?> myChildDef;
|
||||
private final String myChildName;
|
||||
|
@ -1066,10 +1029,40 @@ public abstract class BaseParser implements IParser {
|
|||
* EncodeContext is a shared state object that is passed around the
|
||||
* encode process
|
||||
*/
|
||||
public class EncodeContext extends EncodeContextPath {
|
||||
class EncodeContext extends EncodeContextPath {
|
||||
private final Map<Key, List<BaseParser.CompositeChildElement>> myCompositeChildrenCache = new HashMap<>();
|
||||
private final List<EncodeContextPath> myEncodeElementPaths;
|
||||
private final Set<String> myEncodeElementsAppliesToResourceTypes;
|
||||
private final List<EncodeContextPath> myDontEncodeElementPaths;
|
||||
|
||||
public Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
|
||||
public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) {
|
||||
Collection<String> encodeElements = theParser.myEncodeElements;
|
||||
Collection<String> dontEncodeElements = theParser.myDontEncodeElements;
|
||||
if (isSummaryMode()) {
|
||||
encodeElements = CollectionUtil.nullSafeUnion(
|
||||
encodeElements, theParserOptions.getEncodeElementsForSummaryMode());
|
||||
dontEncodeElements = CollectionUtil.nullSafeUnion(
|
||||
dontEncodeElements, theParserOptions.getDontEncodeElementsForSummaryMode());
|
||||
}
|
||||
|
||||
if (encodeElements == null || encodeElements.isEmpty()) {
|
||||
myEncodeElementPaths = null;
|
||||
} else {
|
||||
myEncodeElementPaths =
|
||||
encodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
|
||||
}
|
||||
if (dontEncodeElements == null || dontEncodeElements.isEmpty()) {
|
||||
myDontEncodeElementPaths = null;
|
||||
} else {
|
||||
myDontEncodeElementPaths =
|
||||
dontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
myEncodeElementsAppliesToResourceTypes =
|
||||
ParserUtil.determineApplicableResourceTypesForTerserPaths(myEncodeElementPaths);
|
||||
}
|
||||
|
||||
private Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
|
||||
return myCompositeChildrenCache;
|
||||
}
|
||||
}
|
||||
|
@ -1161,14 +1154,14 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
private boolean checkIfParentShouldBeEncodedAndBuildPath() {
|
||||
List<EncodeContextPath> encodeElements = myEncodeElements;
|
||||
List<EncodeContextPath> encodeElements = myEncodeContext.myEncodeElementPaths;
|
||||
|
||||
String currentResourceName = myEncodeContext
|
||||
.getResourcePath()
|
||||
.get(myEncodeContext.getResourcePath().size() - 1)
|
||||
.getName();
|
||||
if (myEncodeElementsAppliesToResourceTypes != null
|
||||
&& !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
|
||||
if (myEncodeContext.myEncodeElementsAppliesToResourceTypes != null
|
||||
&& !myEncodeContext.myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
|
||||
encodeElements = null;
|
||||
}
|
||||
|
||||
|
@ -1194,7 +1187,7 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
private boolean checkIfParentShouldNotBeEncodedAndBuildPath() {
|
||||
return checkIfPathMatchesForEncoding(myDontEncodeElements, false);
|
||||
return checkIfPathMatchesForEncoding(myEncodeContext.myDontEncodeElementPaths, false);
|
||||
}
|
||||
|
||||
private boolean checkIfPathMatchesForEncoding(
|
||||
|
@ -1256,16 +1249,7 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
public boolean shouldBeEncoded(boolean theContainedResource) {
|
||||
boolean retVal = true;
|
||||
if (myEncodeElements != null) {
|
||||
retVal = checkIfParentShouldBeEncodedAndBuildPath();
|
||||
}
|
||||
if (retVal && myDontEncodeElements != null) {
|
||||
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
|
||||
}
|
||||
if (theContainedResource) {
|
||||
retVal = !notEncodeForContainedResource.contains(myDef.getElementName());
|
||||
}
|
||||
if (retVal && isSummaryMode() && (getDef() == null || !getDef().isSummary())) {
|
||||
if (isSummaryMode() && (getDef() == null || !getDef().isSummary())) {
|
||||
String resourceName = myEncodeContext.getLeafResourceName();
|
||||
// Technically the spec says we shouldn't include extensions in CapabilityStatement
|
||||
// but we will do so because there are people who depend on this behaviour, at least
|
||||
|
@ -1280,6 +1264,15 @@ public abstract class BaseParser implements IParser {
|
|||
retVal = false;
|
||||
}
|
||||
}
|
||||
if (myEncodeContext.myEncodeElementPaths != null) {
|
||||
retVal = checkIfParentShouldBeEncodedAndBuildPath();
|
||||
}
|
||||
if (retVal && myEncodeContext.myDontEncodeElementPaths != null) {
|
||||
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath();
|
||||
}
|
||||
if (theContainedResource) {
|
||||
retVal = !notEncodeForContainedResource.contains(myDef.getElementName());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -106,6 +109,7 @@ public interface IParser {
|
|||
/**
|
||||
* When encoding, force this resource ID to be encoded as the resource ID
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
IParser setEncodeForceResourceId(IIdType theForceResourceId);
|
||||
|
||||
/**
|
||||
|
@ -155,7 +159,7 @@ public interface IParser {
|
|||
* ID will not have an ID.
|
||||
* <p>
|
||||
* If the resource being encoded is a Bundle or Parameters resource, this setting only applies to the
|
||||
* outer resource being encoded, not any resources contained wihthin.
|
||||
* outer resource being encoded, not any resources contained within.
|
||||
* </p>
|
||||
*
|
||||
* @param theOmitResourceId Should resource IDs be omitted
|
||||
|
@ -172,7 +176,7 @@ public interface IParser {
|
|||
* links. In that case, this value should be set to <code>false</code>.
|
||||
*
|
||||
* @return Returns the parser instance's configuration setting for stripping versions from resource references when
|
||||
* encoding. This method will retun <code>null</code> if no value is set, in which case
|
||||
* encoding. This method will return <code>null</code> if no value is set, in which case
|
||||
* the value from the {@link ParserOptions} will be used (default is <code>true</code>)
|
||||
* @see ParserOptions
|
||||
*/
|
||||
|
@ -199,13 +203,29 @@ public interface IParser {
|
|||
/**
|
||||
* Is the parser in "summary mode"? See {@link #setSummaryMode(boolean)} for information
|
||||
*
|
||||
* @see {@link #setSummaryMode(boolean)} for information
|
||||
* @see #setSummaryMode(boolean) for information
|
||||
*/
|
||||
boolean isSummaryMode();
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>) only elements marked by the FHIR specification as
|
||||
* being "summary elements" will be included.
|
||||
* <p>
|
||||
* It is possible to modify the default summary mode element inclusions
|
||||
* for this parser instance by invoking {@link #setEncodeElements(Set)}
|
||||
* or {@link #setDontEncodeElements(Collection)}. It is also possible to
|
||||
* modify the default summary mode element inclusions for all parsers
|
||||
* generated for a given {@link FhirContext} by accessing
|
||||
* {@link FhirContext#getParserOptions()} followed by
|
||||
* {@link ParserOptions#setEncodeElementsForSummaryMode(Collection)} and/or
|
||||
* {@link ParserOptions#setDontEncodeElementsForSummaryMode(Collection)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* For compatibility reasons with other frameworks, when encoding a
|
||||
* <code>CapabilityStatement</code> resource in summary mode, extensions
|
||||
* are always encoded, even though the FHIR Specification does not consider
|
||||
* them to be summary elements.
|
||||
* </p>
|
||||
*
|
||||
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
|
||||
*/
|
||||
|
@ -287,16 +307,48 @@ public interface IParser {
|
|||
* wildcard)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: If {@link #setSummaryMode(boolean)} is set to <code>true</code>, then any
|
||||
* elements specified using this method will be excluded even if they are
|
||||
* summary elements.
|
||||
* </p>
|
||||
* <p>
|
||||
* DSTU2 note: Note that values including meta, such as <code>Patient.meta</code>
|
||||
* will work for DSTU2 parsers, but values with subelements on meta such
|
||||
* will work for DSTU2 parsers, but values with sub-elements on meta such
|
||||
* as <code>Patient.meta.lastUpdated</code> will only work in
|
||||
* DSTU3+ mode.
|
||||
* </p>
|
||||
*
|
||||
* @param theDontEncodeElements The elements to encode
|
||||
* @param theDontEncodeElements The elements to not encode, or <code>null</code>
|
||||
* @see #setEncodeElements(Set)
|
||||
* @see ParserOptions#setDontEncodeElementsForSummaryMode(Collection)
|
||||
*/
|
||||
IParser setDontEncodeElements(Collection<String> theDontEncodeElements);
|
||||
IParser setDontEncodeElements(@Nullable Collection<String> theDontEncodeElements);
|
||||
|
||||
/**
|
||||
* If provided, specifies the elements which should NOT be encoded. Valid values for this
|
||||
* field would include:
|
||||
* <ul>
|
||||
* <li><b>Patient</b> - Don't encode patient and all its children</li>
|
||||
* <li><b>Patient.name</b> - Don't encode the patient's name</li>
|
||||
* <li><b>Patient.name.family</b> - Don't encode the patient's family name</li>
|
||||
* <li><b>*.text</b> - Don't encode the text element on any resource (only the very first position may contain a
|
||||
* wildcard)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* DSTU2 note: Note that values including meta, such as <code>Patient.meta</code>
|
||||
* will work for DSTU2 parsers, but values with sub-elements on meta such
|
||||
* as <code>Patient.meta.lastUpdated</code> will only work in
|
||||
* DSTU3+ mode.
|
||||
* </p>
|
||||
*
|
||||
* @param theDontEncodeElements The elements to not encode. Can be an empty list, but must not be <code>null</code>.
|
||||
* @see #setDontEncodeElements(Collection)
|
||||
* @see ParserOptions#setDontEncodeElementsForSummaryMode(Collection)
|
||||
* @since 7.4.0
|
||||
*/
|
||||
default IParser setDontEncodeElements(@Nonnull String... theDontEncodeElements) {
|
||||
return setDontEncodeElements(CollectionUtil.newSet(theDontEncodeElements));
|
||||
}
|
||||
|
||||
/**
|
||||
* If provided, specifies the elements which should be encoded, to the exclusion of all others. Valid values for this
|
||||
|
@ -309,11 +361,44 @@ public interface IParser {
|
|||
* wildcard)</li>
|
||||
* <li><b>*.(mandatory)</b> - This is a special case which causes any mandatory fields (min > 0) to be encoded</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: If {@link #setSummaryMode(boolean)} is set to <code>true</code>, then any
|
||||
* elements specified using this method will be included even if they are not
|
||||
* summary elements.
|
||||
* </p>
|
||||
*
|
||||
* @param theEncodeElements The elements to encode
|
||||
* @param theEncodeElements The elements to encode, or <code>null</code>
|
||||
* @see #setDontEncodeElements(Collection)
|
||||
* @see #setEncodeElements(String...)
|
||||
* @see ParserOptions#setEncodeElementsForSummaryMode(Collection)
|
||||
*/
|
||||
IParser setEncodeElements(Set<String> theEncodeElements);
|
||||
IParser setEncodeElements(@Nullable Set<String> theEncodeElements);
|
||||
|
||||
/**
|
||||
* If provided, specifies the elements which should be encoded, to the exclusion of all others. Valid values for this
|
||||
* field would include:
|
||||
* <ul>
|
||||
* <li><b>Patient</b> - Encode patient and all its children</li>
|
||||
* <li><b>Patient.name</b> - Encode only the patient's name</li>
|
||||
* <li><b>Patient.name.family</b> - Encode only the patient's family name</li>
|
||||
* <li><b>*.text</b> - Encode the text element on any resource (only the very first position may contain a
|
||||
* wildcard)</li>
|
||||
* <li><b>*.(mandatory)</b> - This is a special case which causes any mandatory fields (min > 0) to be encoded</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: If {@link #setSummaryMode(boolean)} is set to <code>true</code>, then any
|
||||
* elements specified using this method will be included even if they are not
|
||||
* summary elements.
|
||||
* </p>
|
||||
*
|
||||
* @param theEncodeElements The elements to encode. Can be an empty list, but must not be <code>null</code>.
|
||||
* @since 7.4.0
|
||||
* @see #setEncodeElements(Set)
|
||||
* @see ParserOptions#setEncodeElementsForSummaryMode(String...)
|
||||
*/
|
||||
default IParser setEncodeElements(@Nonnull String... theEncodeElements) {
|
||||
return setEncodeElements(CollectionUtil.newSet(theEncodeElements));
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is false), the values supplied
|
||||
|
@ -351,7 +436,7 @@ public interface IParser {
|
|||
* Sets the server's base URL used by this parser. If a value is set, resource references will be turned into
|
||||
* relative references if they are provided as absolute URLs but have a base matching the given base.
|
||||
*
|
||||
* @param theUrl The base URL, e.g. "http://example.com/base"
|
||||
* @param theUrl The base URL, e.g. "<a href="http://example.com/base">http://example.com/base</a>"
|
||||
* @return Returns an instance of <code>this</code> parser so that method calls can be chained together
|
||||
*/
|
||||
IParser setServerBaseUrl(String theUrl);
|
||||
|
@ -378,7 +463,7 @@ public interface IParser {
|
|||
/**
|
||||
* Returns the value supplied to {@link IParser#setDontStripVersionsFromReferencesAtPaths(String...)}
|
||||
* or <code>null</code> if no value has been set for this parser (in which case the default from
|
||||
* the {@link ParserOptions} will be used}
|
||||
* the {@link ParserOptions} will be used).
|
||||
*
|
||||
* @see #setDontStripVersionsFromReferencesAtPaths(String...)
|
||||
* @see #setStripVersionsFromReferences(Boolean)
|
||||
|
|
|
@ -284,6 +284,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
throws IOException {
|
||||
|
||||
switch (theChildDef.getChildType()) {
|
||||
case EXTENSION_DECLARED:
|
||||
break;
|
||||
case ID_DATATYPE: {
|
||||
IIdType value = (IIdType) theNextValue;
|
||||
String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
|
||||
|
@ -797,7 +799,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
|
||||
private boolean isSupportsFhirComment() {
|
||||
if (myIsSupportsFhirComment == null) {
|
||||
myIsSupportsFhirComment = isFhirVersionLessThanOrEqualTo(FhirVersionEnum.DSTU2_1);
|
||||
myIsSupportsFhirComment = !getContext().getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1);
|
||||
}
|
||||
return myIsSupportsFhirComment;
|
||||
}
|
||||
|
@ -836,7 +838,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
+ theResource.getStructureFhirVersionEnum());
|
||||
}
|
||||
|
||||
EncodeContext encodeContext = new EncodeContext();
|
||||
EncodeContext encodeContext = new EncodeContext(this, getContext().getParserOptions());
|
||||
String resourceName = getContext().getResourceType(theResource);
|
||||
encodeContext.pushPath(resourceName, true);
|
||||
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
|
||||
|
@ -887,7 +889,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
EncodeContext theEncodeContext)
|
||||
throws IOException {
|
||||
|
||||
if (!super.shouldEncodeResource(theResDef.getName())) {
|
||||
if (!super.shouldEncodeResource(theResDef.getName(), theEncodeContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -973,15 +975,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
|
||||
|
||||
if (super.shouldEncodeResourceMeta(resource)
|
||||
if (super.shouldEncodeResourceMeta(resource, theEncodeContext)
|
||||
&& (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false)
|
||||
|| !extensionMetadataKeys.isEmpty()) {
|
||||
beginObject(theEventWriter, "meta");
|
||||
|
||||
if (shouldEncodePath(resource, "meta.versionId")) {
|
||||
if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) {
|
||||
writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
|
||||
}
|
||||
if (shouldEncodePath(resource, "meta.lastUpdated")) {
|
||||
if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) {
|
||||
writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.parser;
|
||||
|
||||
import ca.uhn.fhir.parser.path.EncodeContextPath;
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ParserUtil {
|
||||
|
||||
/** Non instantiable */
|
||||
private ParserUtil() {}
|
||||
|
||||
public static @Nullable Set<String> determineApplicableResourceTypesForTerserPaths(
|
||||
@Nullable List<EncodeContextPath> encodeElements) {
|
||||
Set<String> encodeElementsAppliesToResourceTypes = null;
|
||||
if (encodeElements != null) {
|
||||
encodeElementsAppliesToResourceTypes = new HashSet<>();
|
||||
for (String next : encodeElements.stream()
|
||||
.map(t -> t.getPath().get(0).getName())
|
||||
.collect(Collectors.toList())) {
|
||||
if (next.startsWith("*")) {
|
||||
encodeElementsAppliesToResourceTypes = null;
|
||||
break;
|
||||
}
|
||||
int dotIdx = next.indexOf('.');
|
||||
if (dotIdx == -1) {
|
||||
encodeElementsAppliesToResourceTypes.add(next);
|
||||
} else {
|
||||
encodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx));
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodeElementsAppliesToResourceTypes;
|
||||
}
|
||||
}
|
|
@ -345,12 +345,12 @@ public class RDFParser extends BaseParser {
|
|||
final BaseRuntimeElementDefinition<?> childDef,
|
||||
final boolean includedResource,
|
||||
final CompositeChildElement parent,
|
||||
final EncodeContext encodeContext,
|
||||
final EncodeContext theEncodeContext,
|
||||
final Integer cardinalityIndex) {
|
||||
|
||||
String childGenericName = childDefinition.getElementName();
|
||||
|
||||
encodeContext.pushPath(childGenericName, false);
|
||||
theEncodeContext.pushPath(childGenericName, false);
|
||||
try {
|
||||
|
||||
if (element == null || element.isEmpty()) {
|
||||
|
@ -412,8 +412,8 @@ public class RDFParser extends BaseParser {
|
|||
rdfModel,
|
||||
extensionResource,
|
||||
false,
|
||||
new CompositeChildElement(resDef, encodeContext),
|
||||
encodeContext);
|
||||
new CompositeChildElement(resDef, theEncodeContext),
|
||||
theEncodeContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ public class RDFParser extends BaseParser {
|
|||
String idPredicate = null;
|
||||
if (element instanceof IBaseResource) {
|
||||
idPredicate = FHIR_NS + RESOURCE_ID;
|
||||
IIdType resourceId = processResourceID((IBaseResource) element, encodeContext);
|
||||
IIdType resourceId = processResourceID((IBaseResource) element, theEncodeContext);
|
||||
if (resourceId != null) {
|
||||
idString = resourceId.getIdPart();
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ public class RDFParser extends BaseParser {
|
|||
rdfModel.createProperty(idPredicate), createFhirValueBlankNode(rdfModel, idString));
|
||||
}
|
||||
rdfModel = encodeCompositeElementToStreamWriter(
|
||||
resource, element, rdfModel, rdfResource, includedResource, parent, encodeContext);
|
||||
resource, element, rdfModel, rdfResource, includedResource, parent, theEncodeContext);
|
||||
break;
|
||||
}
|
||||
case CONTAINED_RESOURCE_LIST:
|
||||
|
@ -465,7 +465,7 @@ public class RDFParser extends BaseParser {
|
|||
rdfModel,
|
||||
true,
|
||||
super.fixContainedResourceId(resourceId.getValue()),
|
||||
encodeContext,
|
||||
theEncodeContext,
|
||||
false,
|
||||
containedResource);
|
||||
}
|
||||
|
@ -474,13 +474,14 @@ public class RDFParser extends BaseParser {
|
|||
case RESOURCE: {
|
||||
IBaseResource baseResource = (IBaseResource) element;
|
||||
String resourceName = getContext().getResourceType(baseResource);
|
||||
if (!super.shouldEncodeResource(resourceName)) {
|
||||
if (!super.shouldEncodeResource(resourceName, theEncodeContext)) {
|
||||
break;
|
||||
}
|
||||
encodeContext.pushPath(resourceName, true);
|
||||
IIdType resourceId = processResourceID(resource, encodeContext);
|
||||
encodeResourceToRDFStreamWriter(resource, rdfModel, false, resourceId, encodeContext, false, null);
|
||||
encodeContext.popPath();
|
||||
theEncodeContext.pushPath(resourceName, true);
|
||||
IIdType resourceId = processResourceID(resource, theEncodeContext);
|
||||
encodeResourceToRDFStreamWriter(
|
||||
resource, rdfModel, false, resourceId, theEncodeContext, false, null);
|
||||
theEncodeContext.popPath();
|
||||
break;
|
||||
}
|
||||
case PRIMITIVE_XHTML:
|
||||
|
@ -502,7 +503,7 @@ public class RDFParser extends BaseParser {
|
|||
}
|
||||
}
|
||||
} finally {
|
||||
encodeContext.popPath();
|
||||
theEncodeContext.popPath();
|
||||
}
|
||||
|
||||
return rdfModel;
|
||||
|
|
|
@ -372,7 +372,7 @@ public class XmlParser extends BaseParser {
|
|||
case RESOURCE: {
|
||||
IBaseResource resource = (IBaseResource) theElement;
|
||||
String resourceName = getContext().getResourceType(resource);
|
||||
if (!super.shouldEncodeResource(resourceName)) {
|
||||
if (!super.shouldEncodeResource(resourceName, theEncodeContext)) {
|
||||
break;
|
||||
}
|
||||
theEventWriter.writeStartElement(theChildName);
|
||||
|
@ -736,14 +736,14 @@ public class XmlParser extends BaseParser {
|
|||
|
||||
TagList tags = getMetaTagsForEncoding((resource), theEncodeContext);
|
||||
|
||||
if (super.shouldEncodeResourceMeta(resource)
|
||||
if (super.shouldEncodeResourceMeta(resource, theEncodeContext)
|
||||
&& ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
|
||||
theEventWriter.writeStartElement("meta");
|
||||
if (shouldEncodePath(resource, "meta.versionId")) {
|
||||
if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) {
|
||||
writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
|
||||
}
|
||||
if (updated != null) {
|
||||
if (shouldEncodePath(resource, "meta.lastUpdated")) {
|
||||
if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) {
|
||||
writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,17 +19,76 @@
|
|||
*/
|
||||
package ca.uhn.fhir.util;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
|
||||
public class CollectionUtil {
|
||||
|
||||
public static <T> Set<T> newSet(T... theValues) {
|
||||
HashSet<T> retVal = new HashSet<T>();
|
||||
/**
|
||||
* Non instantiable
|
||||
*/
|
||||
private CollectionUtil() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
for (T t : theValues) {
|
||||
retVal.add(t);
|
||||
/**
|
||||
* Returns an immutable union of both collections. If either or both arguments are
|
||||
* <code>null</code> they will be treated as an empty collection, meaning
|
||||
* that even if both arguments are <code>null</code>, an empty immutable
|
||||
* collection will be returned.
|
||||
* <p>
|
||||
* DO NOT use this method if the underlying collections can be changed
|
||||
* after calling this method, as the behaviour is indeterminate.
|
||||
* </p>
|
||||
*
|
||||
* @param theCollection0 The first set in the union, or <code>null</code>.
|
||||
* @param theCollection1 The second set in the union, or <code>null</code>.
|
||||
* @return Returns a union of both collections. Will not return <code>null</code> ever.
|
||||
* @since 7.4.0
|
||||
*/
|
||||
@Nonnull
|
||||
public static <T> Collection<T> nullSafeUnion(
|
||||
@Nullable Collection<T> theCollection0, @Nullable Collection<T> theCollection1) {
|
||||
Collection<T> collection0 = theCollection0;
|
||||
if (collection0 != null && collection0.isEmpty()) {
|
||||
collection0 = null;
|
||||
}
|
||||
return retVal;
|
||||
Collection<T> collection1 = theCollection1;
|
||||
if (collection1 != null && collection1.isEmpty()) {
|
||||
collection1 = null;
|
||||
}
|
||||
if (collection0 == null && collection1 == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
if (collection0 == null) {
|
||||
return unmodifiableCollection(collection1);
|
||||
}
|
||||
if (collection1 == null) {
|
||||
return unmodifiableCollection(collection0);
|
||||
}
|
||||
return CollectionUtils.union(collection0, collection1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is equivalent to <code>Set.of(...)</code> but is kept here
|
||||
* and used instead of that method because Set.of is not present on Android
|
||||
* SDKs (at least up to 29).
|
||||
* <p>
|
||||
* Sets returned by this method are unmodifiable.
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Set<T> newSet(T... theValues) {
|
||||
HashSet<T> retVal = new HashSet<>();
|
||||
Collections.addAll(retVal, theValues);
|
||||
return Collections.unmodifiableSet(retVal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,8 +90,8 @@ import java.util.Set;
|
|||
public class SearchParameter extends BaseQueryParameter {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
private static HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
|
||||
private static HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
|
||||
private static final HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
|
||||
private static final HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
|
||||
static final String QUALIFIER_ANY_TYPE = ":*";
|
||||
|
||||
static {
|
||||
|
|
|
@ -20,15 +20,18 @@
|
|||
package ca.uhn.hapi.fhir.docs;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Parser {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void main(String[] args) throws DataFormatException, IOException {
|
||||
|
||||
{
|
||||
|
@ -117,6 +120,36 @@ public class Parser {
|
|||
System.out.println(serialized);
|
||||
// END SNIPPET: encodingConfig
|
||||
}
|
||||
{
|
||||
// Create a FHIR context
|
||||
FhirContext ctx = FhirContext.forR4();
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("Simpson").addGiven("James");
|
||||
|
||||
// START SNIPPET: encodingSummary
|
||||
// Create a parser
|
||||
IParser parser = ctx.newJsonParser();
|
||||
|
||||
// Instruct the parser to only include summary elements
|
||||
parser.setSummaryMode(true);
|
||||
|
||||
// If you need to, you can instruct the parser to override
|
||||
// the default summary elements by adding and/or removing
|
||||
// elements from the list of elements it will include. This
|
||||
// is typically not needed, but it's shown here in case you
|
||||
// need to do this:
|
||||
// Include a non-summary element in the summary view.
|
||||
parser.setEncodeElements("Patient.maritalStatus");
|
||||
// Exclude a summary element even though it would normally
|
||||
// be included.
|
||||
parser.setDontEncodeElements("Patient.name");
|
||||
|
||||
// Serialize it
|
||||
String serialized = parser.encodeResourceToString(patient);
|
||||
System.out.println(serialized);
|
||||
// END SNIPPET: encodingSummary
|
||||
}
|
||||
|
||||
{
|
||||
// START SNIPPET: disableStripVersions
|
||||
FhirContext ctx = FhirContext.forR4();
|
||||
|
@ -148,5 +181,37 @@ public class Parser {
|
|||
// END SNIPPET: disableStripVersionsField
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
IBaseResource patient = new Patient();
|
||||
|
||||
// START SNIPPET: globalParserConfig
|
||||
FhirContext ctx = FhirContext.forR4();
|
||||
|
||||
// Request the ParserOptions, which store global config
|
||||
// settings applied to all parsers coming from the given
|
||||
// context.
|
||||
ParserOptions parserOptions = ctx.getParserOptions();
|
||||
|
||||
// Never strip resource reference versions for the following
|
||||
// paths
|
||||
parserOptions.setDontStripVersionsFromReferencesAtPaths(
|
||||
"AuditEvent.entity.reference", "Patient.managingOrganization");
|
||||
|
||||
// Never strip any resource reference versions (setting this
|
||||
// to false would make the setting above redundant since this
|
||||
// setting applies to all paths)
|
||||
parserOptions.setStripVersionsFromReferences(false);
|
||||
|
||||
// Even in summary mode, always include extensions on the
|
||||
// root of Patient resources.
|
||||
parserOptions.setEncodeElementsForSummaryMode("Patient.extension");
|
||||
|
||||
// Create a parser and encode, with the global config applied.
|
||||
IParser parser = ctx.newJsonParser();
|
||||
String encoded = parser.encodeResourceToString(patient);
|
||||
// END SNIPPET: globalParserConfig
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: add
|
||||
issue: 5871
|
||||
title: "When encoding resources in summary mode, it is now possible to override the
|
||||
built-in list of summary elements, by adding additional elements and/or by
|
||||
removing elements from the default list. This can be done for an individual parser
|
||||
instance, or globally using the ParserOptions object available from the FhirContext."
|
|
@ -26,16 +26,39 @@ The following example shows a JSON Parser being used to serialize a FHIR resourc
|
|||
|
||||
By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed output:
|
||||
|
||||
When using the [HAPI FHIR Server](../server_plain/), pretty printing can be requested by adding the parameter <code>_pretty=true</code> to the request.
|
||||
|
||||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingPretty}}
|
||||
```
|
||||
|
||||
## Encoding Configuration
|
||||
|
||||
There are plenty of other options too that can be used to control the output by the parser. A few examples are shown below. See the [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) JavaDoc for more information.
|
||||
There are plenty of other options too, that can be used to control the output by the parser. A few examples are shown below. See the [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) JavaDoc for more information.
|
||||
|
||||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingConfig}}
|
||||
```
|
||||
|
||||
|
||||
## Summary Mode
|
||||
|
||||
For each resource type, the FHIR specification defines a collection of elements which are considered "summary elements". These are marked on the individual resource views using a Sigma (Σ) symbol next to the element names. See the [Patient Resource Definition](https://hl7.org/fhir/patient.html) for an example, looking for
|
||||
this symbol on the page.
|
||||
|
||||
If the parser is configured as shown below, only the summary mode elements will be included in the encoded resource.
|
||||
|
||||
When using the [HAPI FHIR Server](../server_plain/), summary mode can be requested by adding the parameter <code>_summary=true</code> to the request.
|
||||
|
||||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingSummary}}
|
||||
```
|
||||
|
||||
<a name="parser-options"/>
|
||||
|
||||
# Global Parser Configuration
|
||||
|
||||
It is possible to configure a number of parser settings globally for a given FhirContext, meaning that they will apply to all parsers that are created by that context. This is especially useful for [HAPI FHIR Clients](../client/) and [HAPI FHIR Servers](../server_plain/), where parsers are created by the client/server internally using the given FhirContext.
|
||||
|
||||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|globalParserConfig}}
|
||||
```
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static ca.uhn.fhir.util.CollectionUtil.nullSafeUnion;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
|
||||
class CollectionUtilTest {
|
||||
|
||||
@Test
|
||||
void testNullSafeUnion() {
|
||||
assertThat(nullSafeUnion(null, null), empty());
|
||||
assertThat(nullSafeUnion(Set.of(), Set.of()), empty());
|
||||
assertThat(nullSafeUnion(Set.of("A"), null), containsInAnyOrder("A"));
|
||||
assertThat(nullSafeUnion(Set.of("A"), Set.of()), containsInAnyOrder("A"));
|
||||
assertThat(nullSafeUnion(null, Set.of("B")), containsInAnyOrder("B"));
|
||||
assertThat(nullSafeUnion(Set.of(), Set.of("B")), containsInAnyOrder("B"));
|
||||
assertThat(nullSafeUnion(Set.of("A"), Set.of("B")), containsInAnyOrder("A", "B"));
|
||||
assertThat(nullSafeUnion(List.of("A"), Set.of("B")), containsInAnyOrder("A", "B"));
|
||||
}
|
||||
|
||||
}
|
|
@ -74,7 +74,6 @@ import ca.uhn.fhir.rest.param.binder.QueryParameterTypeBinder;
|
|||
import ca.uhn.fhir.rest.param.binder.StringBinder;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -105,30 +104,27 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
ourParamTypes.put(StringParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
ourParamTypes.put(StringOrListParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
ourParamTypes.put(StringAndListParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.STRING,
|
||||
CollectionUtil.newSet(
|
||||
Constants.PARAMQUALIFIER_STRING_EXACT,
|
||||
Constants.PARAMQUALIFIER_STRING_CONTAINS,
|
||||
Constants.PARAMQUALIFIER_MISSING,
|
||||
EMPTY_STRING));
|
||||
ourParamQualifiers.put(RestSearchParameterTypeEnum.STRING, Set.of(new String[] {
|
||||
Constants.PARAMQUALIFIER_STRING_EXACT,
|
||||
Constants.PARAMQUALIFIER_STRING_CONTAINS,
|
||||
Constants.PARAMQUALIFIER_MISSING,
|
||||
EMPTY_STRING
|
||||
}));
|
||||
|
||||
ourParamTypes.put(UriParam.class, RestSearchParameterTypeEnum.URI);
|
||||
ourParamTypes.put(UriOrListParam.class, RestSearchParameterTypeEnum.URI);
|
||||
ourParamTypes.put(UriAndListParam.class, RestSearchParameterTypeEnum.URI);
|
||||
// TODO: are these right for URI?
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.URI,
|
||||
CollectionUtil.newSet(
|
||||
Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
ourParamQualifiers.put(RestSearchParameterTypeEnum.URI, Set.of(new String[] {
|
||||
Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING
|
||||
}));
|
||||
|
||||
ourParamTypes.put(TokenParam.class, RestSearchParameterTypeEnum.TOKEN);
|
||||
ourParamTypes.put(TokenOrListParam.class, RestSearchParameterTypeEnum.TOKEN);
|
||||
ourParamTypes.put(TokenAndListParam.class, RestSearchParameterTypeEnum.TOKEN);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.TOKEN,
|
||||
CollectionUtil.newSet(
|
||||
Constants.PARAMQUALIFIER_TOKEN_TEXT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
ourParamQualifiers.put(RestSearchParameterTypeEnum.TOKEN, Set.of(new String[] {
|
||||
Constants.PARAMQUALIFIER_TOKEN_TEXT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING
|
||||
}));
|
||||
|
||||
ourParamTypes.put(DateParam.class, RestSearchParameterTypeEnum.DATE);
|
||||
ourParamTypes.put(DateOrListParam.class, RestSearchParameterTypeEnum.DATE);
|
||||
|
@ -136,35 +132,35 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
ourParamTypes.put(DateRangeParam.class, RestSearchParameterTypeEnum.DATE);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.DATE,
|
||||
CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING}));
|
||||
|
||||
ourParamTypes.put(QuantityParam.class, RestSearchParameterTypeEnum.QUANTITY);
|
||||
ourParamTypes.put(QuantityOrListParam.class, RestSearchParameterTypeEnum.QUANTITY);
|
||||
ourParamTypes.put(QuantityAndListParam.class, RestSearchParameterTypeEnum.QUANTITY);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.QUANTITY,
|
||||
CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING}));
|
||||
|
||||
ourParamTypes.put(NumberParam.class, RestSearchParameterTypeEnum.NUMBER);
|
||||
ourParamTypes.put(NumberOrListParam.class, RestSearchParameterTypeEnum.NUMBER);
|
||||
ourParamTypes.put(NumberAndListParam.class, RestSearchParameterTypeEnum.NUMBER);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.NUMBER,
|
||||
CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING}));
|
||||
|
||||
ourParamTypes.put(ReferenceParam.class, RestSearchParameterTypeEnum.REFERENCE);
|
||||
ourParamTypes.put(ReferenceOrListParam.class, RestSearchParameterTypeEnum.REFERENCE);
|
||||
ourParamTypes.put(ReferenceAndListParam.class, RestSearchParameterTypeEnum.REFERENCE);
|
||||
// --vvvv-- no empty because that gets added from OptionalParam#chainWhitelist
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.REFERENCE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING));
|
||||
RestSearchParameterTypeEnum.REFERENCE, Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING}));
|
||||
|
||||
ourParamTypes.put(CompositeParam.class, RestSearchParameterTypeEnum.COMPOSITE);
|
||||
ourParamTypes.put(CompositeOrListParam.class, RestSearchParameterTypeEnum.COMPOSITE);
|
||||
ourParamTypes.put(CompositeAndListParam.class, RestSearchParameterTypeEnum.COMPOSITE);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.COMPOSITE,
|
||||
CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING}));
|
||||
|
||||
ourParamTypes.put(HasParam.class, RestSearchParameterTypeEnum.HAS);
|
||||
ourParamTypes.put(HasOrListParam.class, RestSearchParameterTypeEnum.HAS);
|
||||
|
@ -174,7 +170,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
ourParamTypes.put(SpecialOrListParam.class, RestSearchParameterTypeEnum.SPECIAL);
|
||||
ourParamTypes.put(SpecialAndListParam.class, RestSearchParameterTypeEnum.SPECIAL);
|
||||
ourParamQualifiers.put(
|
||||
RestSearchParameterTypeEnum.SPECIAL, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING));
|
||||
RestSearchParameterTypeEnum.SPECIAL, Set.of(new String[] {Constants.PARAMQUALIFIER_MISSING}));
|
||||
}
|
||||
|
||||
private List<Class<? extends IQueryParameterType>> myCompositeTypes = Collections.emptyList();
|
||||
|
|
|
@ -1160,99 +1160,6 @@ public class JsonParserDstu3Test {
|
|||
assertThat(enc, containsString("\"valueId\": \"1\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSummary() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDivAsString("<div>THE DIV</div>");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
patient.addPhoto().setTitle("green");
|
||||
patient.getMaritalStatus().addCoding().setCode("D");
|
||||
|
||||
ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).setSummaryMode(true).encodeResourceToString(patient);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("Patient"));
|
||||
assertThat(encoded, stringContainsInOrder("\"tag\"", "\"system\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_SYSTEM_DSTU3 + "\",", "\"code\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_CODE + "\""));
|
||||
assertThat(encoded, not(containsString("THE DIV")));
|
||||
assertThat(encoded, containsString("family"));
|
||||
assertThat(encoded, not(containsString("maritalStatus")));
|
||||
}
|
||||
|
||||
/**
|
||||
* We specifically include extensions on CapabilityStatment even in
|
||||
* summary mode, since this is behaviour that people depend on
|
||||
*/
|
||||
@Test
|
||||
public void testEncodeSummaryCapabilityStatementExtensions() {
|
||||
|
||||
CapabilityStatement cs = new CapabilityStatement();
|
||||
CapabilityStatement.CapabilityStatementRestComponent rest = cs.addRest();
|
||||
rest.setMode(CapabilityStatement.RestfulCapabilityMode.CLIENT);
|
||||
rest.getSecurity()
|
||||
.addExtension()
|
||||
.setUrl("http://foo")
|
||||
.setValue(new StringType("bar"));
|
||||
|
||||
cs.getVersionElement().addExtension()
|
||||
.setUrl("http://goo")
|
||||
.setValue(new StringType("ber"));
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setSummaryMode(true).setPrettyPrint(true).setPrettyPrint(true).encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, (containsString("http://foo")));
|
||||
assertThat(encoded, (containsString("bar")));
|
||||
assertThat(encoded, (containsString("http://goo")));
|
||||
assertThat(encoded, (containsString("ber")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSummaryPatientExtensions() {
|
||||
|
||||
Patient cs = new Patient();
|
||||
Address address = cs.addAddress();
|
||||
address.setCity("CITY");
|
||||
address
|
||||
.addExtension()
|
||||
.setUrl("http://foo")
|
||||
.setValue(new StringType("bar"));
|
||||
address.getCityElement().addExtension()
|
||||
.setUrl("http://goo")
|
||||
.setValue(new StringType("ber"));
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setSummaryMode(true).setPrettyPrint(true).setPrettyPrint(true).encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://goo")));
|
||||
assertThat(encoded, not(containsString("ber")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSummary2() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDivAsString("<div>THE DIV</div>");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
patient.getMaritalStatus().addCoding().setCode("D");
|
||||
|
||||
patient.getMeta().addTag().setSystem("foo").setCode("bar");
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).setSummaryMode(true).encodeResourceToString(patient);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("Patient"));
|
||||
assertThat(encoded, stringContainsInOrder("\"tag\"", "\"system\": \"foo\",", "\"code\": \"bar\"", "\"system\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_SYSTEM_DSTU3 + "\"",
|
||||
"\"code\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_CODE + "\""));
|
||||
assertThat(encoded, not(containsString("THE DIV")));
|
||||
assertThat(encoded, containsString("family"));
|
||||
assertThat(encoded, not(containsString("maritalStatus")));
|
||||
}
|
||||
|
||||
/**
|
||||
* See #205
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
package ca.uhn.fhir.parser;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.hl7.fhir.r5.model.Address;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Patient;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.stringContainsInOrder;
|
||||
|
||||
public class JsonParserSummaryModeR5Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(JsonParserSummaryModeR5Test.class);
|
||||
|
||||
private static final FhirContext ourCtx = FhirContext.forR5Cached();
|
||||
|
||||
@Test
|
||||
public void testEncodeSummary() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDivAsString("<div>THE DIV</div>");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
patient.addPhoto().setTitle("green");
|
||||
patient.getMaritalStatus().addCoding().setCode("D");
|
||||
|
||||
ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).setSummaryMode(true).encodeResourceToString(patient);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("Patient"));
|
||||
assertThat(encoded, stringContainsInOrder("\"tag\"", "\"system\": \"" + Constants.TAG_SUBSETTED_SYSTEM_R4 + "\",", "\"code\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_CODE + "\""));
|
||||
assertThat(encoded, not(containsString("THE DIV")));
|
||||
assertThat(encoded, containsString("family"));
|
||||
assertThat(encoded, not(containsString("maritalStatus")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSummary2() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDivAsString("<div>THE DIV</div>");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
patient.getMaritalStatus().addCoding().setCode("D");
|
||||
|
||||
patient.getMeta().addTag().setSystem("foo").setCode("bar");
|
||||
|
||||
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).setSummaryMode(true).encodeResourceToString(patient);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("Patient"));
|
||||
assertThat(encoded, stringContainsInOrder("\"tag\"", "\"system\": \"foo\",", "\"code\": \"bar\"", "\"system\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_SYSTEM_R4 + "\"",
|
||||
"\"code\": \"" + ca.uhn.fhir.rest.api.Constants.TAG_SUBSETTED_CODE + "\""));
|
||||
assertThat(encoded, not(containsString("THE DIV")));
|
||||
assertThat(encoded, containsString("family"));
|
||||
assertThat(encoded, not(containsString("maritalStatus")));
|
||||
}
|
||||
|
||||
/**
|
||||
* We specifically include extensions on CapabilityStatment even in
|
||||
* summary mode, since this is behaviour that people depend on
|
||||
*/
|
||||
@Test
|
||||
public void testEncodeSummaryCapabilityStatementExtensions() {
|
||||
CapabilityStatement cs = createCapabilityStatementWithExtensions();
|
||||
|
||||
IParser parser = ourCtx.newJsonParser();
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, (containsString("\"rest\"")));
|
||||
assertThat(encoded, (containsString("http://foo")));
|
||||
assertThat(encoded, (containsString("bar")));
|
||||
assertThat(encoded, (containsString("http://goo")));
|
||||
assertThat(encoded, (containsString("ber")));
|
||||
}
|
||||
|
||||
/**
|
||||
* We specifically include extensions on CapabilityStatment even in
|
||||
* summary mode, since this is behaviour that people depend on
|
||||
*/
|
||||
@Test
|
||||
public void testEncodeSummaryCapabilityStatementExtensions_ExplicitlyExcludeExtensions() {
|
||||
CapabilityStatement cs = createCapabilityStatementWithExtensions();
|
||||
|
||||
IParser parser = ourCtx.newJsonParser();
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
parser.setPrettyPrint(true);
|
||||
parser.setDontEncodeElements(
|
||||
"CapabilityStatement.version.extension",
|
||||
"CapabilityStatement.rest.security.extension"
|
||||
);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, (containsString("\"rest\"")));
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://goo")));
|
||||
assertThat(encoded, not(containsString("ber")));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDontIncludeExtensions() {
|
||||
Patient cs = createPatientWithVariousFieldsAndExtensions();
|
||||
|
||||
IParser parser = ourCtx.newJsonParser();
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("\"id\": \"1\""));
|
||||
assertThat(encoded, containsString("\"versionId\": \"1\""));
|
||||
assertThat(encoded, containsString("\"city\": \"CITY\""));
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://goo")));
|
||||
assertThat(encoded, not(containsString("ber")));
|
||||
assertThat(encoded, not(containsString("http://fog")));
|
||||
assertThat(encoded, not(containsString("baz")));
|
||||
assertThat(encoded, not(containsString("Married to work")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceInclude() {
|
||||
Patient cs = createPatientWithVariousFieldsAndExtensions();
|
||||
|
||||
IParser parser = ourCtx.newJsonParser();
|
||||
parser.setEncodeElements("Patient.maritalStatus", "Patient.address.city.extension");
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("\"id\": \"1\""));
|
||||
assertThat(encoded, containsString("\"versionId\": \"1\""));
|
||||
assertThat(encoded, containsString("\"city\": \"CITY\""));
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://fog")));
|
||||
assertThat(encoded, not(containsString("baz")));
|
||||
assertThat(encoded, containsString("http://goo"));
|
||||
assertThat(encoded, containsString("ber"));
|
||||
assertThat(encoded, containsString("Married to work"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceInclude_UsingStar() {
|
||||
Patient cs = createPatientWithVariousFieldsAndExtensions();
|
||||
|
||||
IParser parser = ourCtx.newJsonParser();
|
||||
parser.setEncodeElements("*.maritalStatus", "*.address.city.extension");
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("\"id\": \"1\""));
|
||||
assertThat(encoded, containsString("\"versionId\": \"1\""));
|
||||
assertThat(encoded, containsString("\"city\": \"CITY\""));
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://fog")));
|
||||
assertThat(encoded, not(containsString("baz")));
|
||||
assertThat(encoded, containsString("http://goo"));
|
||||
assertThat(encoded, containsString("ber"));
|
||||
assertThat(encoded, containsString("Married to work"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceInclude_ViaDefaultConfig() {
|
||||
Patient cs = createPatientWithVariousFieldsAndExtensions();
|
||||
|
||||
FhirContext ctx = FhirContext.forR5();
|
||||
ctx.getParserOptions().setEncodeElementsForSummaryMode("Patient.maritalStatus", "Patient.address.city.extension");
|
||||
ctx.getParserOptions().setDontEncodeElementsForSummaryMode("Patient.id");
|
||||
|
||||
IParser parser = ctx.newJsonParser();
|
||||
parser.setSummaryMode(true);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, not(containsString("\"id\": \"1\"")));
|
||||
assertThat(encoded, containsString("\"versionId\": \"1\""));
|
||||
assertThat(encoded, containsString("\"city\": \"CITY\""));
|
||||
assertThat(encoded, not(containsString("http://foo")));
|
||||
assertThat(encoded, not(containsString("bar")));
|
||||
assertThat(encoded, not(containsString("http://fog")));
|
||||
assertThat(encoded, not(containsString("baz")));
|
||||
assertThat(encoded, containsString("http://goo"));
|
||||
assertThat(encoded, containsString("ber"));
|
||||
assertThat(encoded, containsString("Married to work"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParserOptionsDontIncludeForSummaryModeDoesntApplyIfNotUsingSummaryMode() {
|
||||
Patient cs = createPatientWithVariousFieldsAndExtensions();
|
||||
|
||||
FhirContext ctx = FhirContext.forR5();
|
||||
ctx.getParserOptions().setEncodeElementsForSummaryMode("Patient.maritalStatus", "Patient.address.city.extension");
|
||||
ctx.getParserOptions().setDontEncodeElementsForSummaryMode("Patient.id");
|
||||
|
||||
IParser parser = ctx.newJsonParser();
|
||||
parser.setSummaryMode(false);
|
||||
parser.setPrettyPrint(true);
|
||||
String encoded = parser.encodeResourceToString(cs);
|
||||
ourLog.info(encoded);
|
||||
|
||||
assertThat(encoded, containsString("\"id\": \"1\""));
|
||||
assertThat(encoded, containsString("\"versionId\": \"1\""));
|
||||
assertThat(encoded, containsString("\"city\": \"CITY\""));
|
||||
assertThat(encoded, containsString("http://foo"));
|
||||
assertThat(encoded, containsString("bar"));
|
||||
assertThat(encoded, containsString("http://fog"));
|
||||
assertThat(encoded, containsString("baz"));
|
||||
assertThat(encoded, containsString("http://goo"));
|
||||
assertThat(encoded, containsString("ber"));
|
||||
assertThat(encoded, containsString("Married to work"));
|
||||
}
|
||||
|
||||
private static @Nonnull CapabilityStatement createCapabilityStatementWithExtensions() {
|
||||
CapabilityStatement cs = new CapabilityStatement();
|
||||
CapabilityStatement.CapabilityStatementRestComponent rest = cs.addRest();
|
||||
rest.setMode(CapabilityStatement.RestfulCapabilityMode.CLIENT);
|
||||
rest.getSecurity()
|
||||
.addExtension()
|
||||
.setUrl("http://foo")
|
||||
.setValue(new StringType("bar"));
|
||||
|
||||
cs.getVersionElement().addExtension()
|
||||
.setUrl("http://goo")
|
||||
.setValue(new StringType("ber"));
|
||||
return cs;
|
||||
}
|
||||
|
||||
private static @Nonnull Patient createPatientWithVariousFieldsAndExtensions() {
|
||||
Patient retVal = new Patient();
|
||||
retVal.setId("Patient/1/_history/1");
|
||||
retVal.getMaritalStatus().setText("Married to work");
|
||||
retVal.addExtension()
|
||||
.setUrl("http://fog")
|
||||
.setValue(new StringType("baz"));
|
||||
Address address = retVal.addAddress();
|
||||
address.setCity("CITY");
|
||||
address
|
||||
.addExtension()
|
||||
.setUrl("http://foo")
|
||||
.setValue(new StringType("bar"));
|
||||
address.getCityElement().addExtension()
|
||||
.setUrl("http://goo")
|
||||
.setValue(new StringType("ber"));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,6 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.conformance;
|
||||
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
|
||||
|
@ -127,7 +126,7 @@ public class DateSearchTestCase {
|
|||
*/
|
||||
@Nonnull
|
||||
static List<DateSearchTestCase> expandPrefixCases(Reader theSource, String theFileName) {
|
||||
Set<String> supportedPrefixes = CollectionUtil.newSet("eq", "ge", "gt", "le", "lt", "ne");
|
||||
Set<String> supportedPrefixes = Set.of(new String[] {"eq", "ge", "gt", "le", "lt", "ne"});
|
||||
|
||||
// expand these into individual tests for each prefix.
|
||||
LineNumberReader lineNumberReader = new LineNumberReader(theSource);
|
||||
|
|
Loading…
Reference in New Issue