Revert "Start working on FHIRPatch"

This reverts commit b3f6e7d521.
This commit is contained in:
jamesagnew 2020-05-17 11:56:50 -04:00
parent b3f6e7d521
commit cc92bd7b07
23 changed files with 544 additions and 1713 deletions

View File

@ -164,9 +164,6 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
}
if (theClear) {
existingList.clear();
if (theValue == null) {
return;
}
}
existingList.add(theValue);
}

View File

@ -24,21 +24,14 @@ import ca.uhn.fhir.rest.annotation.Patch;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
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}
*/
public enum PatchTypeEnum {
JSON_PATCH(Constants.CT_JSON_PATCH),
XML_PATCH(Constants.CT_XML_PATCH),
FHIR_PATCH_JSON(Constants.CT_FHIR_JSON_NEW),
FHIR_PATCH_XML(Constants.CT_FHIR_XML_NEW);
XML_PATCH(Constants.CT_XML_PATCH);
private static volatile Map<String, PatchTypeEnum> ourContentTypeToPatchType;
private final String myContentType;
PatchTypeEnum(String theContentType) {
@ -49,7 +42,6 @@ public enum PatchTypeEnum {
return myContentType;
}
@Nonnull
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(String theContentType) {
String contentType = theContentType;
int semiColonIdx = contentType.indexOf(';');
@ -57,22 +49,12 @@ public enum PatchTypeEnum {
contentType = theContentType.substring(0, semiColonIdx);
}
contentType = contentType.trim();
Map<String, PatchTypeEnum> map = ourContentTypeToPatchType;
if (map == null) {
map = new HashMap<>();
for (PatchTypeEnum next : values()) {
map.put(next.getContentType(), next);
}
ourContentTypeToPatchType = map;
}
PatchTypeEnum retVal = map.get(contentType);
if (retVal == null) {
if (Constants.CT_JSON_PATCH.equals(contentType)) {
return JSON_PATCH;
} else if (Constants.CT_XML_PATCH.equals(contentType)) {
return XML_PATCH;
} else {
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + UrlUtil.sanitizeUrlPart(theContentType));
}
return retVal;
}
}

View File

@ -20,23 +20,15 @@ package ca.uhn.fhir.rest.gclient;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IPatch {
/**
* 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
*
* @param thePatchBody The body of the patch
* @param thePatchBody
* The body of the patch
*/
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);
}

View File

@ -1,17 +1,7 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.*;
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.IResource;
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.parser.DataFormatException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
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 org.hl7.fhir.instance.model.api.*;
import java.util.ArrayList;
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.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
/*
* #%L
@ -267,11 +241,6 @@ public class FhirTerser {
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) {
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
* type {@link Object}.
*
* @param theElement The element 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 theResource 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}.
*/
public List<IBase> getValues(IBase theElement, String thePath) {
public List<IBase> getValues(IBaseResource theResource, String thePath) {
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
* 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 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}.
*/
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;
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
* 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 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.
* @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;
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
* 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 theWantedClass The desired class to be returned in a list.
* @param <T> Type declared by <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) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
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
* 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 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 <T> Type declared by <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) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
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
* 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 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.
@ -588,10 +557,10 @@ public class FhirTerser {
* @param <T> Type declared by <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) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
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) {
@ -832,7 +801,7 @@ public class FhirTerser {
}
/**
* Visit all elements in a given resource or element
* Visit all elements in a given resource
* <p>
* <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
* </p>
@ -841,19 +810,12 @@ public class FhirTerser {
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
* </p>
*
* @param theElement The element to visit
* @param theResource The resource to visit
* @param theVisitor The visitor
*/
public void visit(IBase theElement, IModelVisitor2 theVisitor) {
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
if (def instanceof BaseRuntimeElementCompositeDefinition) {
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());
}
public void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
visit(theResource, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
@ -1009,5 +971,4 @@ public class FhirTerser {
});
}
}

View File

@ -20,26 +20,17 @@ package ca.uhn.fhir.util;
* #L%
*/
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.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.model.primitive.StringDt;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.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 org.hl7.fhir.instance.model.api.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@ -62,66 +53,27 @@ public class ParametersUtil {
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");
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
return parameterReps
.stream()
.filter(param -> {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(param.getClass());
List<T> retVal = new ArrayList<>();
for (IBase nextParameter : parameterReps) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
List<IBase> nameValues = nameChild.getAccessor().getValues(param);
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())) {
return false;
}
return true;
})
.collect(Collectors.toList());
continue;
}
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<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
for (IBase nextParameter : namedParameters) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
valueValues
@ -285,13 +237,6 @@ public class ParametersUtil {
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) {
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
value.setValue(theValue);
@ -339,5 +284,4 @@ public class ParametersUtil {
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
}
}

View File

@ -31,42 +31,16 @@ import org.apache.commons.text.StringEscapeUtils;
import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.io.EscapingWriterFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.FactoryConfigurationError;
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.*;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
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 java.io.*;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -1539,11 +1513,8 @@ public class XmlUtil {
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
}
/**
* Non-instantiable
*/
private XmlUtil() {
}
/** Non-instantiable */
private XmlUtil() {}
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
@Override
@ -1864,7 +1835,7 @@ public class XmlUtil {
}
public static Document parseDocument(String theInput) throws IOException, SAXException {
DocumentBuilder builder;
DocumentBuilder builder = null;
try {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
@ -1889,13 +1860,4 @@ public class XmlUtil {
InputSource src = new InputSource(new StringReader(theInput));
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();
}
}

View File

@ -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.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.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.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}

View File

@ -1617,16 +1617,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
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
public IPatchExecutable withId(IIdType theId) {
if (theId == null) {

View File

@ -32,22 +32,10 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Provenance;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.*;
import java.util.ArrayList;
import java.util.List;
@ -79,72 +67,6 @@ public class GenericClientExample {
// END SNIPPET: dontValidate
}
public static void patchFhir() {
// Create a context
FhirContext ctx = FhirContext.forR4();
// START SNIPPET: patchFhir
// Create a client
String serverBase = "http://hapi.fhir.org/baseR4";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Create a patch object
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]"));
// Invoke the patch
MethodOutcome outcome = client
.patch()
.withFhirPatch(patch)
.withId("Patient/123")
.execute();
// The server may provide the updated contents in the response
Patient resultingResource = (Patient) outcome.getResource();
// END SNIPPET: patchFhir
}
public static void patchJson() {
// Create a context
FhirContext ctx = FhirContext.forR4();
// START SNIPPET: patchJson
// Create a client
String serverBase = "http://hapi.fhir.org/baseR4";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Create a JSON patch object
String patch = "[ " +
" { " +
" \"op\":\"replace\", " +
" \"path\":\"/active\", " +
" \"value\":false " +
" } " +
"]";
// Invoke the patch
MethodOutcome outcome = client
.patch()
.withBody(patch)
.withId("Patient/123")
.execute();
// The server may provide the updated contents in the response
Patient resultingResource = (Patient) outcome.getResource();
// END SNIPPET: patchJson
}
public static void simpleExample() {
// START SNIPPET: simple
// We're connecting to a DSTU1 compliant server in this example
@ -519,6 +441,7 @@ public class GenericClientExample {
}
}
@SuppressWarnings("unused")
@ -549,7 +472,6 @@ public class GenericClientExample {
public static void main(String[] args) {
paging();
}
private static void paging() {
{
// START SNIPPET: searchPaging

View File

@ -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.
# 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
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.

View File

@ -562,11 +562,6 @@
<artifactId>embedded-elasticsearch</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>

View File

@ -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.delete.DeleteConflictService;
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.jpa.model.entity.BaseHasResource;
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.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
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.ValidationOptions;
import ca.uhn.fhir.validation.ValidationResult;
import com.github.fge.jsonpatch.JsonPatch;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
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.IIdType;
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 destination;
switch (thePatchType) {
case JSON_PATCH:
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
break;
case XML_PATCH:
} else {
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")

View File

@ -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 ()
}
}
}
}

View File

@ -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.rest.api.Constants;

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
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.entity.ContentType;
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.r4.model.*;
import org.junit.Test;
@ -28,39 +26,6 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test {
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
public void testPatchAddArray() throws IOException {
IIdType id;

View File

@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.util.jsonpatch;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.r4.model.Observation;
import org.junit.Test;

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.rest.server.method;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.trim;
/*
* #%L
@ -46,14 +45,6 @@ class PatchTypeParameter implements IParameter {
public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
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);
}

View File

@ -870,11 +870,6 @@
<artifactId>commons-csv</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
<version>1.1.14-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>