parent
b3f6e7d521
commit
cc92bd7b07
|
@ -164,9 +164,6 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
|
||||||
}
|
}
|
||||||
if (theClear) {
|
if (theClear) {
|
||||||
existingList.clear();
|
existingList.clear();
|
||||||
if (theValue == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
existingList.add(theValue);
|
existingList.add(theValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,21 +24,14 @@ import ca.uhn.fhir.rest.annotation.Patch;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter type for methods annotated with {@link Patch}
|
* Parameter type for methods annotated with {@link Patch}
|
||||||
*/
|
*/
|
||||||
public enum PatchTypeEnum {
|
public enum PatchTypeEnum {
|
||||||
|
|
||||||
JSON_PATCH(Constants.CT_JSON_PATCH),
|
JSON_PATCH(Constants.CT_JSON_PATCH),
|
||||||
XML_PATCH(Constants.CT_XML_PATCH),
|
XML_PATCH(Constants.CT_XML_PATCH);
|
||||||
FHIR_PATCH_JSON(Constants.CT_FHIR_JSON_NEW),
|
|
||||||
FHIR_PATCH_XML(Constants.CT_FHIR_XML_NEW);
|
|
||||||
|
|
||||||
private static volatile Map<String, PatchTypeEnum> ourContentTypeToPatchType;
|
|
||||||
private final String myContentType;
|
private final String myContentType;
|
||||||
|
|
||||||
PatchTypeEnum(String theContentType) {
|
PatchTypeEnum(String theContentType) {
|
||||||
|
@ -49,7 +42,6 @@ public enum PatchTypeEnum {
|
||||||
return myContentType;
|
return myContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(String theContentType) {
|
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(String theContentType) {
|
||||||
String contentType = theContentType;
|
String contentType = theContentType;
|
||||||
int semiColonIdx = contentType.indexOf(';');
|
int semiColonIdx = contentType.indexOf(';');
|
||||||
|
@ -57,22 +49,12 @@ public enum PatchTypeEnum {
|
||||||
contentType = theContentType.substring(0, semiColonIdx);
|
contentType = theContentType.substring(0, semiColonIdx);
|
||||||
}
|
}
|
||||||
contentType = contentType.trim();
|
contentType = contentType.trim();
|
||||||
|
if (Constants.CT_JSON_PATCH.equals(contentType)) {
|
||||||
|
return JSON_PATCH;
|
||||||
Map<String, PatchTypeEnum> map = ourContentTypeToPatchType;
|
} else if (Constants.CT_XML_PATCH.equals(contentType)) {
|
||||||
if (map == null) {
|
return XML_PATCH;
|
||||||
map = new HashMap<>();
|
} else {
|
||||||
for (PatchTypeEnum next : values()) {
|
|
||||||
map.put(next.getContentType(), next);
|
|
||||||
}
|
|
||||||
ourContentTypeToPatchType = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchTypeEnum retVal = map.get(contentType);
|
|
||||||
if (retVal == null) {
|
|
||||||
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + UrlUtil.sanitizeUrlPart(theContentType));
|
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + UrlUtil.sanitizeUrlPart(theContentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,23 +20,15 @@ package ca.uhn.fhir.rest.gclient;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|
||||||
|
|
||||||
public interface IPatch {
|
public interface IPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The body of the patch document serialized in either XML or JSON which conforms to
|
* The body of the patch document serialized in either XML or JSON which conforms to
|
||||||
* http://jsonpatch.com/ or http://tools.ietf.org/html/rfc5261
|
* http://jsonpatch.com/ or http://tools.ietf.org/html/rfc5261
|
||||||
*
|
*
|
||||||
* @param thePatchBody The body of the patch
|
* @param thePatchBody
|
||||||
|
* The body of the patch
|
||||||
*/
|
*/
|
||||||
IPatchWithBody withBody(String thePatchBody);
|
IPatchWithBody withBody(String thePatchBody);
|
||||||
|
|
||||||
/**
|
|
||||||
* The body of the patch document using FHIR Patch syntax as described at
|
|
||||||
* http://hl7.org/fhir/fhirpatch.html
|
|
||||||
*
|
|
||||||
* @since 5.1.0
|
|
||||||
*/
|
|
||||||
IPatchWithBody withFhirPatch(IBaseParameters thePatchBody);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
package ca.uhn.fhir.util;
|
package ca.uhn.fhir.util;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
import ca.uhn.fhir.context.*;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
|
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.RuntimeChildChoiceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeChildDirectResource;
|
|
||||||
import ca.uhn.fhir.context.RuntimeExtensionDtDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
|
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
|
||||||
|
@ -20,30 +10,14 @@ import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.IdentityHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -267,11 +241,6 @@ public class FhirTerser {
|
||||||
return retVal.get(0);
|
return retVal.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends IBase> Optional<T> getSingleValue(IBase theTarget, String thePath, Class<T> theWantedType) {
|
|
||||||
return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private <T extends IBase> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, IBase theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
|
private <T extends IBase> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, IBase theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
|
||||||
return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
|
return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
|
||||||
}
|
}
|
||||||
|
@ -502,85 +471,85 @@ public class FhirTerser {
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type {@link Object}.
|
* type {@link Object}.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.@param theElement The resource instance to be accessed. Must not be null.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @return A list of values of type {@link Object}.
|
* @return A list of values of type {@link Object}.
|
||||||
*/
|
*/
|
||||||
public List<IBase> getValues(IBase theElement, String thePath) {
|
public List<IBase> getValues(IBaseResource theResource, String thePath) {
|
||||||
Class<IBase> wantedClass = IBase.class;
|
Class<IBase> wantedClass = IBase.class;
|
||||||
|
|
||||||
return getValues(theElement, thePath, wantedClass);
|
return getValues(theResource, thePath, wantedClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type {@link Object}.
|
* type {@link Object}.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
||||||
* @return A list of values of type {@link Object}.
|
* @return A list of values of type {@link Object}.
|
||||||
*/
|
*/
|
||||||
public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate) {
|
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
|
||||||
Class<IBase> wantedClass = IBase.class;
|
Class<IBase> wantedClass = IBase.class;
|
||||||
|
|
||||||
return getValues(theElement, thePath, wantedClass, theCreate);
|
return getValues(theResource, thePath, wantedClass, theCreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type {@link Object}.
|
* type {@link Object}.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
||||||
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
|
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
|
||||||
* @return A list of values of type {@link Object}.
|
* @return A list of values of type {@link Object}.
|
||||||
*/
|
*/
|
||||||
public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) {
|
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
|
||||||
Class<IBase> wantedClass = IBase.class;
|
Class<IBase> wantedClass = IBase.class;
|
||||||
|
|
||||||
return getValues(theElement, thePath, wantedClass, theCreate, theAddExtension);
|
return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type <code>theWantedClass</code>.
|
* type <code>theWantedClass</code>.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @param theWantedClass The desired class to be returned in a list.
|
* @param theWantedClass The desired class to be returned in a list.
|
||||||
* @param <T> Type declared by <code>theWantedClass</code>
|
* @param <T> Type declared by <code>theWantedClass</code>
|
||||||
* @return A list of values of type <code>theWantedClass</code>.
|
* @return A list of values of type <code>theWantedClass</code>.
|
||||||
*/
|
*/
|
||||||
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass) {
|
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
|
||||||
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
|
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
|
||||||
List<String> parts = parsePath(def, thePath);
|
List<String> parts = parsePath(def, thePath);
|
||||||
return getValues(def, theElement, parts, theWantedClass);
|
return getValues(def, theResource, parts, theWantedClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type <code>theWantedClass</code>.
|
* type <code>theWantedClass</code>.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @param theWantedClass The desired class to be returned in a list.
|
* @param theWantedClass The desired class to be returned in a list.
|
||||||
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
||||||
* @param <T> Type declared by <code>theWantedClass</code>
|
* @param <T> Type declared by <code>theWantedClass</code>
|
||||||
* @return A list of values of type <code>theWantedClass</code>.
|
* @return A list of values of type <code>theWantedClass</code>.
|
||||||
*/
|
*/
|
||||||
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate) {
|
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
|
||||||
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
|
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
|
||||||
List<String> parts = parsePath(def, thePath);
|
List<String> parts = parsePath(def, thePath);
|
||||||
return getValues(def, theElement, parts, theWantedClass, theCreate, false);
|
return getValues(def, theResource, parts, theWantedClass, theCreate, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns values stored in an element identified by its path. The list of values is of
|
* Returns values stored in an element identified by its path. The list of values is of
|
||||||
* type <code>theWantedClass</code>.
|
* type <code>theWantedClass</code>.
|
||||||
*
|
*
|
||||||
* @param theElement The element to be accessed. Must not be null.
|
* @param theResource The resource instance to be accessed. Must not be null.
|
||||||
* @param thePath The path for the element to be accessed.
|
* @param thePath The path for the element to be accessed.
|
||||||
* @param theWantedClass The desired class to be returned in a list.
|
* @param theWantedClass The desired class to be returned in a list.
|
||||||
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
|
||||||
|
@ -588,10 +557,10 @@ public class FhirTerser {
|
||||||
* @param <T> Type declared by <code>theWantedClass</code>
|
* @param <T> Type declared by <code>theWantedClass</code>
|
||||||
* @return A list of values of type <code>theWantedClass</code>.
|
* @return A list of values of type <code>theWantedClass</code>.
|
||||||
*/
|
*/
|
||||||
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
|
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
|
||||||
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
|
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
|
||||||
List<String> parts = parsePath(def, thePath);
|
List<String> parts = parsePath(def, thePath);
|
||||||
return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension);
|
return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
|
private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
|
||||||
|
@ -832,7 +801,7 @@ public class FhirTerser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visit all elements in a given resource or element
|
* Visit all elements in a given resource
|
||||||
* <p>
|
* <p>
|
||||||
* <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
|
* <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -841,19 +810,12 @@ public class FhirTerser {
|
||||||
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
|
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theElement The element to visit
|
* @param theResource The resource to visit
|
||||||
* @param theVisitor The visitor
|
* @param theVisitor The visitor
|
||||||
*/
|
*/
|
||||||
public void visit(IBase theElement, IModelVisitor2 theVisitor) {
|
public void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
|
||||||
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
|
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
|
||||||
if (def instanceof BaseRuntimeElementCompositeDefinition) {
|
visit(theResource, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
||||||
BaseRuntimeElementCompositeDefinition<?> defComposite = (BaseRuntimeElementCompositeDefinition<?>) def;
|
|
||||||
visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
|
||||||
} else if (theElement instanceof IBaseExtension) {
|
|
||||||
theVisitor.acceptUndeclaredExtension((IBaseExtension<?, ?>) theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
|
||||||
} else {
|
|
||||||
theVisitor.acceptElement(theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
|
private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
|
||||||
|
@ -1009,5 +971,4 @@ public class FhirTerser {
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,26 +20,17 @@ package ca.uhn.fhir.util;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
import ca.uhn.fhir.context.*;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||||
|
|
||||||
|
@ -54,7 +45,7 @@ public class ParametersUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
||||||
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue();
|
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer)t.getValue();
|
||||||
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
|
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,72 +53,33 @@ public class ParametersUtil {
|
||||||
return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
|
return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IBase> getNamedParameters(FhirContext theCtx, IBaseResource theParameters, String theParameterName) {
|
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
|
||||||
Validate.notNull(theParameters, "theParameters must not be null");
|
Validate.notNull(theParameters, "theParameters must not be null");
|
||||||
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
|
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
|
||||||
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
|
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
|
||||||
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
|
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
|
||||||
|
|
||||||
return parameterReps
|
|
||||||
.stream()
|
|
||||||
.filter(param -> {
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(param.getClass());
|
|
||||||
BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
|
|
||||||
List<IBase> nameValues = nameChild.getAccessor().getValues(param);
|
|
||||||
Optional<? extends IPrimitiveType<?>> nameValue = nameValues
|
|
||||||
.stream()
|
|
||||||
.filter(t -> t instanceof IPrimitiveType<?>)
|
|
||||||
.map(t -> ((IPrimitiveType<?>) t))
|
|
||||||
.findFirst();
|
|
||||||
if (!nameValue.isPresent() || !theParameterName.equals(nameValue.get().getValueAsString())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<IBase> getParameterPart(FhirContext theCtx, IBase theParameter, String theParameterName) {
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theParameter.getClass());
|
|
||||||
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("part");
|
|
||||||
List<IBase> parts = valueChild.getAccessor().getValues(theParameter);
|
|
||||||
|
|
||||||
for (IBase nextPart : parts) {
|
|
||||||
Optional<IPrimitiveType> name = theCtx.newTerser().getSingleValue(nextPart, "name", IPrimitiveType.class);
|
|
||||||
if (name.isPresent() && theParameterName.equals(name.get().getValueAsString())) {
|
|
||||||
return Optional.of(nextPart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<IBase> getParameterPartValue(FhirContext theCtx, IBase theParameter, String theParameterName) {
|
|
||||||
Optional<IBase> part = getParameterPart(theCtx, theParameter, theParameterName);
|
|
||||||
if (part.isPresent()) {
|
|
||||||
return theCtx.newTerser().getSingleValue(part.get(), "value[x]", IBase.class);
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getParameterPartValueAsString(FhirContext theCtx, IBase theParameter, String theParameterName) {
|
|
||||||
return getParameterPartValue(theCtx, theParameter, theParameterName).map(t -> (IPrimitiveType<?>) t).map(t -> t.getValueAsString()).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
|
|
||||||
List<T> retVal = new ArrayList<>();
|
List<T> retVal = new ArrayList<>();
|
||||||
|
|
||||||
List<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
|
for (IBase nextParameter : parameterReps) {
|
||||||
for (IBase nextParameter : namedParameters) {
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
|
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
|
||||||
|
BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
|
||||||
|
List<IBase> nameValues = nameChild.getAccessor().getValues(nextParameter);
|
||||||
|
Optional<? extends IPrimitiveType<?>> nameValue = nameValues
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t instanceof IPrimitiveType<?>)
|
||||||
|
.map(t -> ((IPrimitiveType<?>) t))
|
||||||
|
.findFirst();
|
||||||
|
if (!nameValue.isPresent() || !theParameterName.equals(nameValue.get().getValueAsString())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
|
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
|
||||||
List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
|
List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
|
||||||
valueValues
|
valueValues
|
||||||
.stream()
|
.stream()
|
||||||
.filter(t -> t instanceof IPrimitiveType<?>)
|
.filter(t -> t instanceof IPrimitiveType<?>)
|
||||||
.map(t -> ((IPrimitiveType<?>) t))
|
.map(t->((IPrimitiveType<?>) t))
|
||||||
.map(theMapper)
|
.map(theMapper)
|
||||||
.filter(t -> t != null)
|
.filter(t -> t != null)
|
||||||
.forEach(retVal::add);
|
.forEach(retVal::add);
|
||||||
|
@ -285,13 +237,6 @@ public class ParametersUtil {
|
||||||
addPart(theContext, theParameter, theName, value);
|
addPart(theContext, theParameter, theName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addPartInteger(FhirContext theContext, IBase theParameter, String theName, Integer theInteger) {
|
|
||||||
IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theContext.getElementDefinition("integer").newInstance();
|
|
||||||
value.setValue(theInteger);
|
|
||||||
|
|
||||||
addPart(theContext, theParameter, theName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addPartString(FhirContext theContext, IBase theParameter, String theName, String theValue) {
|
public static void addPartString(FhirContext theContext, IBase theParameter, String theName, String theValue) {
|
||||||
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
|
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
|
||||||
value.setValue(theValue);
|
value.setValue(theValue);
|
||||||
|
@ -339,5 +284,4 @@ public class ParametersUtil {
|
||||||
|
|
||||||
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
|
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,42 +31,16 @@ import org.apache.commons.text.StringEscapeUtils;
|
||||||
import org.codehaus.stax2.XMLOutputFactory2;
|
import org.codehaus.stax2.XMLOutputFactory2;
|
||||||
import org.codehaus.stax2.io.EscapingWriterFactory;
|
import org.codehaus.stax2.io.EscapingWriterFactory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.stream.FactoryConfigurationError;
|
import javax.xml.stream.*;
|
||||||
import javax.xml.stream.XMLEventReader;
|
|
||||||
import javax.xml.stream.XMLEventWriter;
|
|
||||||
import javax.xml.stream.XMLInputFactory;
|
|
||||||
import javax.xml.stream.XMLOutputFactory;
|
|
||||||
import javax.xml.stream.XMLResolver;
|
|
||||||
import javax.xml.stream.XMLStreamException;
|
|
||||||
import javax.xml.stream.XMLStreamWriter;
|
|
||||||
import javax.xml.stream.events.XMLEvent;
|
import javax.xml.stream.events.XMLEvent;
|
||||||
import javax.xml.transform.OutputKeys;
|
import java.io.*;
|
||||||
import javax.xml.transform.Transformer;
|
import java.util.*;
|
||||||
import javax.xml.transform.TransformerConfigurationException;
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import javax.xml.transform.TransformerFactory;
|
|
||||||
import javax.xml.transform.dom.DOMSource;
|
|
||||||
import javax.xml.transform.stream.StreamResult;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
@ -1539,11 +1513,8 @@ public class XmlUtil {
|
||||||
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
|
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Non-instantiable */
|
||||||
* Non-instantiable
|
private XmlUtil() {}
|
||||||
*/
|
|
||||||
private XmlUtil() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1864,7 +1835,7 @@ public class XmlUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Document parseDocument(String theInput) throws IOException, SAXException {
|
public static Document parseDocument(String theInput) throws IOException, SAXException {
|
||||||
DocumentBuilder builder;
|
DocumentBuilder builder = null;
|
||||||
try {
|
try {
|
||||||
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||||
docBuilderFactory.setNamespaceAware(true);
|
docBuilderFactory.setNamespaceAware(true);
|
||||||
|
@ -1889,13 +1860,4 @@ public class XmlUtil {
|
||||||
InputSource src = new InputSource(new StringReader(theInput));
|
InputSource src = new InputSource(new StringReader(theInput));
|
||||||
return builder.parse(src);
|
return builder.parse(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String encodeDocument(Element theElement) throws TransformerException {
|
|
||||||
TransformerFactory transFactory = TransformerFactory.newInstance();
|
|
||||||
Transformer transformer = transFactory.newTransformer();
|
|
||||||
StringWriter buffer = new StringWriter();
|
|
||||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
|
||||||
transformer.transform(new DOMSource(theElement), new StreamResult(buffer));
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,10 +106,6 @@ ca.uhn.fhir.jpa.dao.TransactionProcessor.missingMandatoryResource=Missing requir
|
||||||
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchContentType=Missing or invalid content type for PATCH operation
|
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchContentType=Missing or invalid content type for PATCH operation
|
||||||
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchBody=Unable to determine PATCH body from request
|
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchBody=Unable to determine PATCH body from request
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.patch.FhirPatch.invalidInsertIndex=Invalid insert index {0} for path {1} - Only have {2} existing entries
|
|
||||||
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveSourceIndex=Invalid move source index {0} for path {1} - Only have {2} existing entries
|
|
||||||
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destination index {0} for path {1} - Only have {2} existing entries
|
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||||
|
|
||||||
|
@ -143,7 +139,7 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can
|
||||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
|
||||||
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.patch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1}
|
ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1}
|
||||||
|
|
||||||
|
|
|
@ -1617,16 +1617,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) {
|
|
||||||
Validate.notNull(thePatchBody, "thePatchBody must not be null");
|
|
||||||
|
|
||||||
myPatchType = PatchTypeEnum.FHIR_PATCH_JSON;
|
|
||||||
myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPatchExecutable withId(IIdType theId) {
|
public IPatchExecutable withId(IIdType theId) {
|
||||||
if (theId == null) {
|
if (theId == null) {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -209,22 +209,6 @@ FHIR also specifies a type of update called "conditional updates", where instead
|
||||||
|
|
||||||
**See Also:** See the description of [Update ETags](#update_etags) below for information on specifying a matching version in the client request.
|
**See Also:** See the description of [Update ETags](#update_etags) below for information on specifying a matching version in the client request.
|
||||||
|
|
||||||
# Patch - Instance
|
|
||||||
|
|
||||||
The PATCH operation can be used to modify a resource in place by supplying a delta
|
|
||||||
|
|
||||||
The following example shows how to perform a patch using a [FHIR Patch](http://hl7.org/fhir/fhirpatch.html)
|
|
||||||
|
|
||||||
```java
|
|
||||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|patchFhir}}
|
|
||||||
```
|
|
||||||
|
|
||||||
The following example shows how to perform a patch using a [JSON Patch](https://tools.ietf.org/html/rfc6902.)
|
|
||||||
|
|
||||||
```java
|
|
||||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|patchJson}}
|
|
||||||
```
|
|
||||||
|
|
||||||
# History - Server/Type/Instance
|
# History - Server/Type/Instance
|
||||||
|
|
||||||
To retrieve the version history of all resources, or all resources of a given type, or of a specific instance of a resource, you call the [`history()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#history()) method.
|
To retrieve the version history of all resources, or all resources of a given type, or of a specific instance of a resource, you call the [`history()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#history()) method.
|
||||||
|
|
|
@ -562,11 +562,6 @@
|
||||||
<artifactId>embedded-elasticsearch</artifactId>
|
<artifactId>embedded-elasticsearch</artifactId>
|
||||||
<version>2.10.0</version>
|
<version>2.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.hl7.fhir.testcases</groupId>
|
|
||||||
<artifactId>fhir-test-cases</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
|
|
@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.patch.FhirPatch;
|
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
import ca.uhn.fhir.jpa.model.entity.BaseTag;
|
||||||
|
@ -53,8 +52,8 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
|
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
|
||||||
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
|
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
|
@ -88,12 +87,10 @@ import ca.uhn.fhir.validation.IValidationContext;
|
||||||
import ca.uhn.fhir.validation.IValidatorModule;
|
import ca.uhn.fhir.validation.IValidatorModule;
|
||||||
import ca.uhn.fhir.validation.ValidationOptions;
|
import ca.uhn.fhir.validation.ValidationOptions;
|
||||||
import ca.uhn.fhir.validation.ValidationResult;
|
import ca.uhn.fhir.validation.ValidationResult;
|
||||||
import com.github.fge.jsonpatch.JsonPatch;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -889,24 +886,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
|
|
||||||
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
|
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
|
||||||
IBaseResource destination;
|
IBaseResource destination;
|
||||||
switch (thePatchType) {
|
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
|
||||||
case JSON_PATCH:
|
|
||||||
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||||
break;
|
} else {
|
||||||
case XML_PATCH:
|
|
||||||
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||||
break;
|
|
||||||
case FHIR_PATCH_XML:
|
|
||||||
IBaseParameters fhirPatchJson = (IBaseParameters) getContext().newXmlParser().parseResource(thePatchBody);
|
|
||||||
new FhirPatch(getContext()).apply(resourceToUpdate, fhirPatchJson);
|
|
||||||
destination = resourceToUpdate;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case FHIR_PATCH_JSON:
|
|
||||||
IBaseParameters fhirPatchXml = (IBaseParameters) getContext().newJsonParser().parseResource(thePatchBody);
|
|
||||||
new FhirPatch(getContext()).apply(resourceToUpdate, fhirPatchXml);
|
|
||||||
destination = resourceToUpdate;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -1,316 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.util.IModelVisitor2;
|
|
||||||
import ca.uhn.fhir.util.ParametersUtil;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
public class FhirPatch {
|
|
||||||
|
|
||||||
private final FhirContext myContext;
|
|
||||||
|
|
||||||
public FhirPatch(FhirContext theContext) {
|
|
||||||
myContext = theContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void apply(IBaseResource theResource, IBaseResource thePatch) {
|
|
||||||
|
|
||||||
List<IBase> opParameters = ParametersUtil.getNamedParameters(myContext, thePatch, "operation");
|
|
||||||
for (IBase nextOp : opParameters) {
|
|
||||||
String type = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "type");
|
|
||||||
String path = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "path");
|
|
||||||
Optional<IBase> valuePart = ParametersUtil.getParameterPart(myContext, nextOp, "value");
|
|
||||||
Optional<IBase> valuePartValue = ParametersUtil.getParameterPartValue(myContext, nextOp, "value");
|
|
||||||
|
|
||||||
type = defaultString(type);
|
|
||||||
path = defaultString(path);
|
|
||||||
|
|
||||||
String containingPath;
|
|
||||||
String elementName;
|
|
||||||
Integer removeIndex = null;
|
|
||||||
Integer insertIndex = null;
|
|
||||||
if ("delete".equals(type)) {
|
|
||||||
|
|
||||||
doDelete(theResource, path);
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ("add".equals(type)) {
|
|
||||||
|
|
||||||
containingPath = path;
|
|
||||||
elementName = ParametersUtil.getParameterPartValueAsString(myContext, nextOp, "name");
|
|
||||||
|
|
||||||
} else if ("replace".equals(type)) {
|
|
||||||
|
|
||||||
int lastDot = path.lastIndexOf(".");
|
|
||||||
containingPath = path.substring(0, lastDot);
|
|
||||||
elementName = path.substring(lastDot + 1);
|
|
||||||
|
|
||||||
} else if ("insert".equals(type)) {
|
|
||||||
|
|
||||||
int lastDot = path.lastIndexOf(".");
|
|
||||||
containingPath = path.substring(0, lastDot);
|
|
||||||
elementName = path.substring(lastDot + 1);
|
|
||||||
insertIndex = ParametersUtil
|
|
||||||
.getParameterPartValue(myContext, nextOp, "index")
|
|
||||||
.map(t -> (IPrimitiveType<Integer>) t)
|
|
||||||
.map(t -> t.getValue())
|
|
||||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
|
||||||
|
|
||||||
} else if ("move".equals(type)) {
|
|
||||||
|
|
||||||
int lastDot = path.lastIndexOf(".");
|
|
||||||
containingPath = path.substring(0, lastDot);
|
|
||||||
elementName = path.substring(lastDot + 1);
|
|
||||||
insertIndex = ParametersUtil
|
|
||||||
.getParameterPartValue(myContext, nextOp, "destination")
|
|
||||||
.map(t -> (IPrimitiveType<Integer>) t)
|
|
||||||
.map(t -> t.getValue())
|
|
||||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
|
||||||
removeIndex = ParametersUtil
|
|
||||||
.getParameterPartValue(myContext, nextOp, "source")
|
|
||||||
.map(t -> (IPrimitiveType<Integer>) t)
|
|
||||||
.map(t -> t.getValue())
|
|
||||||
.orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation"));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
throw new InvalidRequestException("Unknown patch operation type: " + type);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, containingPath, IBase.class);
|
|
||||||
for (IBase next : paths) {
|
|
||||||
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> element = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(next.getClass());
|
|
||||||
String childName = elementName;
|
|
||||||
BaseRuntimeChildDefinition childDef = element.getChildByName(childName);
|
|
||||||
BaseRuntimeElementDefinition<?> childElement;
|
|
||||||
if (childDef == null) {
|
|
||||||
childName = elementName + "[x]";
|
|
||||||
childDef = element.getChildByName(childName);
|
|
||||||
childElement = childDef.getChildByName(childDef.getValidChildNames().iterator().next());
|
|
||||||
} else {
|
|
||||||
childElement = childDef.getChildByName(childName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("move".equals(type)) {
|
|
||||||
|
|
||||||
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
|
|
||||||
if (removeIndex >= existingValues.size()) {
|
|
||||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveSourceIndex", removeIndex, path, existingValues.size());
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
IBase newValue = existingValues.remove(removeIndex.intValue());
|
|
||||||
|
|
||||||
if (insertIndex > existingValues.size()) {
|
|
||||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidMoveDestinationIndex", insertIndex, path, existingValues.size());
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
existingValues.add(insertIndex, newValue);
|
|
||||||
|
|
||||||
childDef.getMutator().setValue(next, null);
|
|
||||||
for (IBase nextNewValue : existingValues) {
|
|
||||||
childDef.getMutator().addValue(next, nextNewValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
IBase newValue;
|
|
||||||
if (valuePartValue.isPresent()) {
|
|
||||||
newValue = valuePartValue.get();
|
|
||||||
} else {
|
|
||||||
newValue = childElement.newInstance();
|
|
||||||
|
|
||||||
if (valuePart.isPresent()) {
|
|
||||||
List<IBase> valuePartParts = myContext.newTerser().getValues(valuePart.get(), "part");
|
|
||||||
for (IBase nextValuePartPart : valuePartParts) {
|
|
||||||
|
|
||||||
String name = myContext.newTerser().getSingleValue(nextValuePartPart, "name", IPrimitiveType.class).map(t -> t.getValueAsString()).orElse(null);
|
|
||||||
if (isNotBlank(name)) {
|
|
||||||
|
|
||||||
Optional<IBase> value = myContext.newTerser().getSingleValue(nextValuePartPart, "value[x]", IBase.class);
|
|
||||||
if (value.isPresent()) {
|
|
||||||
|
|
||||||
BaseRuntimeChildDefinition partChildDef = ((BaseRuntimeElementCompositeDefinition<?>) childElement).getChildByName(name);
|
|
||||||
partChildDef.getMutator().addValue(newValue, value.get());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IBaseEnumeration.class.isAssignableFrom(childElement.getImplementingClass()) || XhtmlNode.class.isAssignableFrom(childElement.getImplementingClass())) {
|
|
||||||
// If the element is an IBaseEnumeration, we will use the actual element definition to build one, since
|
|
||||||
// it needs the right factory object passed to its constructor
|
|
||||||
IPrimitiveType<?> newValueInstance = (IPrimitiveType<?>) childElement.newInstance();
|
|
||||||
newValueInstance.setValueAsString(((IPrimitiveType<?>) newValue).getValueAsString());
|
|
||||||
childDef.getMutator().setValue(next, newValueInstance);
|
|
||||||
newValue = newValueInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("insert".equals(type)) {
|
|
||||||
|
|
||||||
List<IBase> existingValues = new ArrayList<>(childDef.getAccessor().getValues(next));
|
|
||||||
if (insertIndex > existingValues.size()) {
|
|
||||||
String msg = myContext.getLocalizer().getMessage(FhirPatch.class, "invalidInsertIndex", insertIndex, path, existingValues.size());
|
|
||||||
throw new InvalidRequestException(msg);
|
|
||||||
}
|
|
||||||
existingValues.add(insertIndex, newValue);
|
|
||||||
|
|
||||||
childDef.getMutator().setValue(next, null);
|
|
||||||
for (IBase nextNewValue : existingValues) {
|
|
||||||
childDef.getMutator().addValue(next, nextNewValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
childDef.getMutator().setValue(next, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void doDelete(IBaseResource theResource, String thePath) {
|
|
||||||
List<IBase> paths = myContext.newFhirPath().evaluate(theResource, thePath, IBase.class);
|
|
||||||
for (IBase next : paths) {
|
|
||||||
myContext.newTerser().visit(next, new IModelVisitor2() {
|
|
||||||
@Override
|
|
||||||
public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
|
||||||
if (theElement instanceof IPrimitiveType) {
|
|
||||||
((IPrimitiveType<?>) theElement).setValueAsString(null);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
|
|
||||||
theNextExt.setUrl(null);
|
|
||||||
theNextExt.setValue(null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBaseParameters diff(IBaseResource theOldValue, IBaseResource theNewValue) {
|
|
||||||
String oldValueTypeName = myContext.getResourceDefinition(theOldValue).getName();
|
|
||||||
String newValueTypeName = myContext.getResourceDefinition(theNewValue).getName();
|
|
||||||
Validate.isTrue(oldValueTypeName.equalsIgnoreCase(newValueTypeName), "Resources must be of same type");
|
|
||||||
|
|
||||||
IBaseParameters retVal = ParametersUtil.newInstance(myContext);
|
|
||||||
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theOldValue).getBaseDefinition();
|
|
||||||
String path = def.getName();
|
|
||||||
|
|
||||||
compare(retVal, def, path, path, theOldValue, theNewValue);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void compare(IBaseParameters theDiff, BaseRuntimeElementDefinition<?> theDef, String theSourcePath, String theTargetPath, IBase theOldField, IBase theNewField) {
|
|
||||||
|
|
||||||
if (theDef instanceof BaseRuntimeElementCompositeDefinition) {
|
|
||||||
|
|
||||||
for (BaseRuntimeChildDefinition nextChild : ((BaseRuntimeElementCompositeDefinition<?>) theDef).getChildren()) {
|
|
||||||
|
|
||||||
List<IBase> sourceValues = nextChild.getAccessor().getValues(theOldField);
|
|
||||||
List<IBase> targetValues = nextChild.getAccessor().getValues(theNewField);
|
|
||||||
|
|
||||||
int sourceIndex = 0;
|
|
||||||
int targetIndex = 0;
|
|
||||||
while (sourceIndex < sourceValues.size() && targetIndex < targetValues.size()) {
|
|
||||||
|
|
||||||
IBase sourceChildField = sourceValues.get(sourceIndex);
|
|
||||||
Validate.notNull(sourceChildField); // not expected to happen, but just in case
|
|
||||||
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(sourceChildField.getClass());
|
|
||||||
IBase targetChildField = targetValues.get(targetIndex);
|
|
||||||
Validate.notNull(targetChildField); // not expected to happen, but just in case
|
|
||||||
String sourcePath = theSourcePath + "." + nextChild.getElementName() + (nextChild.getMax() != 1 ? "[" + sourceIndex + "]" : "");
|
|
||||||
String targetPath = theSourcePath + "." + nextChild.getElementName() + (nextChild.getMax() != 1 ? "[" + targetIndex + "]" : "");
|
|
||||||
|
|
||||||
compare(theDiff, def, sourcePath, targetPath, sourceChildField, targetChildField);
|
|
||||||
|
|
||||||
sourceIndex++;
|
|
||||||
targetIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find newly inserted items
|
|
||||||
while (targetIndex < targetValues.size()) {
|
|
||||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
|
||||||
ParametersUtil.addPartCode(myContext, operation, "type", "insert");
|
|
||||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath + "." + nextChild.getElementName());
|
|
||||||
ParametersUtil.addPartInteger(myContext, operation, "index", targetIndex);
|
|
||||||
ParametersUtil.addPart(myContext, operation, "value", targetValues.get(targetIndex));
|
|
||||||
|
|
||||||
targetIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find deleted items
|
|
||||||
while (sourceIndex < sourceValues.size()) {
|
|
||||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
|
||||||
ParametersUtil.addPartCode(myContext, operation, "type", "delete");
|
|
||||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath + "." + nextChild.getElementName() + (nextChild.getMax() != 1 ? "[" + targetIndex + "]" : ""));
|
|
||||||
|
|
||||||
sourceIndex++;
|
|
||||||
targetIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
BaseRuntimeElementDefinition<?> sourceDef = myContext.getElementDefinition(theOldField.getClass());
|
|
||||||
BaseRuntimeElementDefinition<?> targetDef = myContext.getElementDefinition(theNewField.getClass());
|
|
||||||
if (!sourceDef.getName().equals(targetDef.getName())) {
|
|
||||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
|
||||||
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
|
|
||||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
|
|
||||||
ParametersUtil.addPart(myContext, operation, "value", theNewField);
|
|
||||||
} else if (theOldField instanceof IPrimitiveType) {
|
|
||||||
IPrimitiveType<?> oldPrimitive = (IPrimitiveType<?>) theOldField;
|
|
||||||
IPrimitiveType<?> newPrimitive = (IPrimitiveType<?>) theNewField;
|
|
||||||
if (!Objects.equals(oldPrimitive.getValueAsString(), newPrimitive.getValueAsString())) {
|
|
||||||
IBase operation = ParametersUtil.addParameterToParameters(myContext, theDiff, "operation");
|
|
||||||
ParametersUtil.addPartCode(myContext, operation, "type", "replace");
|
|
||||||
ParametersUtil.addPartString(myContext, operation, "path", theTargetPath);
|
|
||||||
ParametersUtil.addPart(myContext, operation, "value", newPrimitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ()
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
package ca.uhn.fhir.jpa.util.xmlpatch;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
|
@ -1,115 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.test.BaseTest;
|
|
||||||
import ca.uhn.fhir.util.ClasspathUtil;
|
|
||||||
import ca.uhn.fhir.util.XmlUtil;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
public abstract class BaseFhirPatchCoreTest extends BaseTest {
|
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseFhirPatchCoreTest.class);
|
|
||||||
private final String myName;
|
|
||||||
private final String myMode;
|
|
||||||
private final IBaseResource myInput;
|
|
||||||
private final IBaseResource myPatch;
|
|
||||||
private final IBaseResource myOutput;
|
|
||||||
|
|
||||||
public BaseFhirPatchCoreTest(String theName, String theMode, IBaseResource theInput, IBaseResource thePatch, IBaseResource theOutput) {
|
|
||||||
myName = theName;
|
|
||||||
myMode = theMode;
|
|
||||||
myInput = theInput;
|
|
||||||
myPatch = thePatch;
|
|
||||||
myOutput = theOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testApply() {
|
|
||||||
ourLog.info("Testing diff in {} mode: {}", myMode, myName);
|
|
||||||
|
|
||||||
if (myMode.equals("both") || myMode.equals("forwards")) {
|
|
||||||
|
|
||||||
FhirPatch patch = new FhirPatch(getContext());
|
|
||||||
patch.apply(myInput, myPatch);
|
|
||||||
|
|
||||||
String expected = getContext().newJsonParser().setPrettyPrint(true).encodeResourceToString(myOutput);
|
|
||||||
String actual = getContext().newJsonParser().setPrettyPrint(true).encodeResourceToString(myInput);
|
|
||||||
assertEquals(expected, actual);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fail("Unknown mode: " + myMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract FhirContext getContext();
|
|
||||||
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static Collection<Object[]> loadTestSpec(FhirContext theContext, String theTestSpec) throws IOException, SAXException, TransformerException {
|
|
||||||
List<Object[]> retVal = new ArrayList<>();
|
|
||||||
|
|
||||||
String testsString = ClasspathUtil.loadResource(theTestSpec);
|
|
||||||
Document doc = XmlUtil.parseDocument(testsString);
|
|
||||||
Element tests = (Element) doc.getElementsByTagName("tests").item(0);
|
|
||||||
NodeList cases = tests.getElementsByTagName("case");
|
|
||||||
|
|
||||||
for (int i = 0; i < cases.getLength(); i++) {
|
|
||||||
Element next = (Element) cases.item(i);
|
|
||||||
|
|
||||||
String name = next.getAttribute("name");
|
|
||||||
String mode = next.getAttribute("mode");
|
|
||||||
|
|
||||||
Element diffElement = (Element) next.getElementsByTagName("diff").item(0);
|
|
||||||
Element diffParametersElement = getFirstChildElement(diffElement);
|
|
||||||
String encoded = XmlUtil.encodeDocument(diffParametersElement);
|
|
||||||
IBaseResource diff = theContext.newXmlParser().parseResource(encoded);
|
|
||||||
|
|
||||||
Element inputElement = (Element) next.getElementsByTagName("input").item(0);
|
|
||||||
Element inputResourceElement = getFirstChildElement(inputElement);
|
|
||||||
String inputEncoded = XmlUtil.encodeDocument(inputResourceElement);
|
|
||||||
IBaseResource input = theContext.newXmlParser().parseResource(inputEncoded);
|
|
||||||
|
|
||||||
Element outputElement = (Element) next.getElementsByTagName("output").item(0);
|
|
||||||
Element outputResourceElement = getFirstChildElement(outputElement);
|
|
||||||
String outputEncoded = XmlUtil.encodeDocument(outputResourceElement);
|
|
||||||
IBaseResource output = theContext.newXmlParser().parseResource(outputEncoded);
|
|
||||||
|
|
||||||
retVal.add(new Object[]{name, mode, input, diff, output});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Element getFirstChildElement(Element theInput) {
|
|
||||||
for (int i = 0; i < theInput.getChildNodes().getLength(); i++) {
|
|
||||||
if (theInput.getChildNodes().item(i) instanceof Element) {
|
|
||||||
return (Element) theInput.getChildNodes().item(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fail("No child of type Element");
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
public class FhirPatchR4CoreTest extends BaseFhirPatchCoreTest {
|
|
||||||
|
|
||||||
private static final FhirContext ourCtx = FhirContext.forR4();
|
|
||||||
|
|
||||||
public FhirPatchR4CoreTest(String theName, String theMode, IBaseResource theInput, IBaseResource thePatch, IBaseResource theOutput) {
|
|
||||||
super(theName, theMode, theInput, thePatch, theOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected FhirContext getContext() {
|
|
||||||
return ourCtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parameterized.Parameters(name = "{0}")
|
|
||||||
public static Collection<Object[]> parameters() throws IOException, SAXException, TransformerException {
|
|
||||||
String testSpec = "/org/hl7/fhir/testcases/r4/patch/fhir-path-tests.xml";
|
|
||||||
return loadTestSpec(ourCtx, testSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,330 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
|
||||||
import org.hl7.fhir.r4.model.CodeType;
|
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
|
||||||
import org.hl7.fhir.r4.model.Extension;
|
|
||||||
import org.hl7.fhir.r4.model.Identifier;
|
|
||||||
import org.hl7.fhir.r4.model.IntegerType;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
public class FhirPatchR4Test {
|
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirPatchR4Test.class);
|
|
||||||
private static final FhirContext ourCtx = FhirContext.forR4();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidOperation() {
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("foo"));
|
|
||||||
|
|
||||||
try {
|
|
||||||
svc.apply(patient, patch);
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertEquals("Unknown patch operation type: foo", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInsertToInvalidIndex() {
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("insert"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("path")
|
|
||||||
.setValue(new StringType("Patient.identifier"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("index")
|
|
||||||
.setValue(new IntegerType(2));
|
|
||||||
|
|
||||||
try {
|
|
||||||
svc.apply(patient, patch);
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertEquals("Invalid insert index 2 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMoveFromInvalidIndex() {
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("move"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("path")
|
|
||||||
.setValue(new StringType("Patient.identifier"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("source")
|
|
||||||
.setValue(new IntegerType(2));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("destination")
|
|
||||||
.setValue(new IntegerType(1));
|
|
||||||
|
|
||||||
try {
|
|
||||||
svc.apply(patient, patch);
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertEquals("Invalid move source index 2 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMoveToInvalidIndex() {
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.addIdentifier().setSystem("sys");
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("move"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("path")
|
|
||||||
.setValue(new StringType("Patient.identifier"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("source")
|
|
||||||
.setValue(new IntegerType(0));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("destination")
|
|
||||||
.setValue(new IntegerType(1));
|
|
||||||
|
|
||||||
try {
|
|
||||||
svc.apply(patient, patch);
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertEquals("Invalid move destination index 1 for path Patient.identifier - Only have 0 existing entries", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDeleteItemWithExtension() {
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.setActive(true);
|
|
||||||
patient.addIdentifier().addExtension("http://foo", new StringType("abc"));
|
|
||||||
patient.addIdentifier().setSystem("sys").setValue("val");
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("delete"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("path")
|
|
||||||
.setValue(new StringType("Patient.identifier[0]"));
|
|
||||||
|
|
||||||
svc.apply(patient, patch);
|
|
||||||
|
|
||||||
assertEquals("{\"resourceType\":\"Patient\",\"identifier\":[{\"system\":\"sys\",\"value\":\"val\"}],\"active\":true}", ourCtx.newJsonParser().encodeResourceToString(patient));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_ReplaceIdentifier() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.addIdentifier().setSystem("system-0").setValue("value-0");
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.addIdentifier().setSystem("system-1").setValue("value-1");
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(2, diff.getParameter().size());
|
|
||||||
assertEquals("replace", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.identifier[0].system", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
assertEquals("system-1", extractPartValuePrimitive(diff, 0, "operation", "value"));
|
|
||||||
assertEquals("replace", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.identifier[0].value", extractPartValuePrimitive(diff, 1, "operation", "path"));
|
|
||||||
assertEquals("value-1", extractPartValuePrimitive(diff, 1, "operation", "value"));
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_ReplaceChoice() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.setDeceased(new BooleanType(true));
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.setDeceased(new DateTimeType("2020-05-16"));
|
|
||||||
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(1, diff.getParameter().size());
|
|
||||||
assertEquals("replace", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.deceased", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
assertEquals("2020-05-16", extractPartValuePrimitive(diff, 0, "operation", "value"));
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_ReplaceChoice2() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.setDeceased(new DateTimeType("2020-05-16"));
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.setDeceased(new BooleanType(true));
|
|
||||||
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(1, diff.getParameter().size());
|
|
||||||
assertEquals("replace", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.deceased", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
assertEquals("true", extractPartValuePrimitive(diff, 0, "operation", "value"));
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_AddExtensionOnPrimitive() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.setActive(true);
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.setActive(true);
|
|
||||||
newValue.getActiveElement().addExtension("http://foo", new StringType("a value"));
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(1, diff.getParameter().size());
|
|
||||||
assertEquals("insert", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.active.extension", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
assertEquals("0", extractPartValuePrimitive(diff, 0, "operation", "index"));
|
|
||||||
assertEquals("http://foo", extractPartValue(diff, 0, "operation", "value", Extension.class).getUrl());
|
|
||||||
assertEquals("a value", extractPartValue(diff, 0, "operation", "value", Extension.class).getValueAsPrimitive().getValueAsString());
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_InsertIdentifier() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.addIdentifier().setSystem("system-0").setValue("value-0");
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.addIdentifier().setSystem("system-0").setValue("value-0");
|
|
||||||
newValue.addIdentifier().setSystem("system-1").setValue("value-1");
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(1, diff.getParameter().size());
|
|
||||||
assertEquals("insert", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("1", extractPartValuePrimitive(diff, 0, "operation", "index"));
|
|
||||||
assertEquals("Patient.identifier", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
assertEquals("system-1", extractPartValue(diff, 0, "operation", "value", Identifier.class).getSystem());
|
|
||||||
assertEquals("value-1", extractPartValue(diff, 0, "operation", "value", Identifier.class).getValue());
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratePatch_DeleteIdentifier() {
|
|
||||||
Patient oldValue = new Patient();
|
|
||||||
oldValue.addIdentifier().setSystem("system-0").setValue("value-0");
|
|
||||||
oldValue.addIdentifier().setSystem("system-1").setValue("value-1");
|
|
||||||
|
|
||||||
Patient newValue = new Patient();
|
|
||||||
newValue.addIdentifier().setSystem("system-0").setValue("value-0");
|
|
||||||
|
|
||||||
FhirPatch svc = new FhirPatch(ourCtx);
|
|
||||||
Parameters diff = (Parameters) svc.diff(oldValue, newValue);
|
|
||||||
|
|
||||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(diff));
|
|
||||||
|
|
||||||
assertEquals(1, diff.getParameter().size());
|
|
||||||
assertEquals("delete", extractPartValuePrimitive(diff, 0, "operation", "type"));
|
|
||||||
assertEquals("Patient.identifier[1]", extractPartValuePrimitive(diff, 0, "operation", "path"));
|
|
||||||
|
|
||||||
validateDiffProducesSameResults(oldValue, newValue, svc, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateDiffProducesSameResults(Patient theOldValue, Patient theNewValue, FhirPatch theSvc, Parameters theDiff) {
|
|
||||||
theSvc.apply(theOldValue, theDiff);
|
|
||||||
String expected = ourCtx.newJsonParser().encodeResourceToString(theNewValue);
|
|
||||||
String actual = ourCtx.newJsonParser().encodeResourceToString(theOldValue);
|
|
||||||
assertEquals(expected, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String extractPartValuePrimitive(Parameters theDiff, int theIndex, String theParameterName, String thePartName) {
|
|
||||||
Parameters.ParametersParameterComponent component = theDiff.getParameter().stream().filter(t -> t.getName().equals(theParameterName)).collect(Collectors.toList()).get(theIndex);
|
|
||||||
Parameters.ParametersParameterComponent part = component.getPart().stream().filter(t -> t.getName().equals(thePartName)).findFirst().orElseThrow(() -> new IllegalArgumentException());
|
|
||||||
return ((IPrimitiveType) part.getValue()).getValueAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends IBase> T extractPartValue(Parameters theDiff, int theIndex, String theParameterName, String thePartName, Class<T> theExpectedType) {
|
|
||||||
Parameters.ParametersParameterComponent component = theDiff.getParameter().stream().filter(t -> t.getName().equals(theParameterName)).collect(Collectors.toList()).get(theIndex);
|
|
||||||
Parameters.ParametersParameterComponent part = component.getPart().stream().filter(t -> t.getName().equals(thePartName)).findFirst().orElseThrow(() -> new IllegalArgumentException());
|
|
||||||
assert theExpectedType.isAssignableFrom(part.getValue().getClass());
|
|
||||||
return (T) part.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.patch;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.Parameterized;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
|
||||||
public class FhirPatchR5CoreTest extends BaseFhirPatchCoreTest {
|
|
||||||
|
|
||||||
private static final FhirContext ourCtx = FhirContext.forR5();
|
|
||||||
|
|
||||||
public FhirPatchR5CoreTest(String theName, String theMode, IBaseResource theInput, IBaseResource thePatch, IBaseResource theOutput) {
|
|
||||||
super(theName, theMode, theInput, thePatch, theOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected FhirContext getContext() {
|
|
||||||
return ourCtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parameterized.Parameters(name = "{0}")
|
|
||||||
public static Collection<Object[]> parameters() throws IOException, SAXException, TransformerException {
|
|
||||||
String testSpec = "/org/hl7/fhir/testcases/r5/patch/fhir-path-tests.xml";
|
|
||||||
return loadTestSpec(ourCtx, testSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
@ -9,7 +8,6 @@ import org.apache.http.client.methods.HttpPatch;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -28,39 +26,6 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PatchProviderR4Test.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(PatchProviderR4Test.class);
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFhirPatch() {
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.setActive(true);
|
|
||||||
patient.addIdentifier().addExtension("http://foo", new StringType("abc"));
|
|
||||||
patient.addIdentifier().setSystem("sys").setValue("val");
|
|
||||||
IIdType id = ourClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
Parameters patch = new Parameters();
|
|
||||||
Parameters.ParametersParameterComponent operation = patch.addParameter();
|
|
||||||
operation.setName("operation");
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("type")
|
|
||||||
.setValue(new CodeType("delete"));
|
|
||||||
operation
|
|
||||||
.addPart()
|
|
||||||
.setName("path")
|
|
||||||
.setValue(new StringType("Patient.identifier[0]"));
|
|
||||||
|
|
||||||
MethodOutcome outcome = ourClient
|
|
||||||
.patch()
|
|
||||||
.withFhirPatch(patch)
|
|
||||||
.withId(id)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
Patient resultingResource = (Patient) outcome.getResource();
|
|
||||||
assertEquals(1, resultingResource.getIdentifier().size());
|
|
||||||
|
|
||||||
resultingResource = ourClient.read().resource(Patient.class).withId(id).execute();
|
|
||||||
assertEquals(1, resultingResource.getIdentifier().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPatchAddArray() throws IOException {
|
public void testPatchAddArray() throws IOException {
|
||||||
IIdType id;
|
IIdType id;
|
||||||
|
|
|
@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
|
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
|
||||||
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ca.uhn.fhir.rest.server.method;
|
package ca.uhn.fhir.rest.server.method;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.trim;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -46,14 +45,6 @@ class PatchTypeParameter implements IParameter {
|
||||||
|
|
||||||
public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
|
public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
|
||||||
String contentTypeAll = defaultString(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE));
|
String contentTypeAll = defaultString(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE));
|
||||||
|
|
||||||
int semicolonIndex = contentTypeAll.indexOf(';');
|
|
||||||
if (semicolonIndex > 0) {
|
|
||||||
contentTypeAll = contentTypeAll.substring(0, semicolonIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
contentTypeAll = trim(contentTypeAll);
|
|
||||||
|
|
||||||
return PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentTypeAll);
|
return PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentTypeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
pom.xml
5
pom.xml
|
@ -870,11 +870,6 @@
|
||||||
<artifactId>commons-csv</artifactId>
|
<artifactId>commons-csv</artifactId>
|
||||||
<version>1.7</version>
|
<version>1.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.hl7.fhir.testcases</groupId>
|
|
||||||
<artifactId>fhir-test-cases</artifactId>
|
|
||||||
<version>1.1.14-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains</groupId>
|
<groupId>org.jetbrains</groupId>
|
||||||
<artifactId>annotations</artifactId>
|
<artifactId>annotations</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue