From cc92bd7b0708754cfca2438aae86b49cb0c7aba5 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sun, 17 May 2020 11:56:50 -0400 Subject: [PATCH] Revert "Start working on FHIRPatch" This reverts commit b3f6e7d521c1451186fb180db6d8538e9f5dfcd8. --- .../BaseRuntimeDeclaredChildDefinition.java | 3 - .../ca/uhn/fhir/rest/api/PatchTypeEnum.java | 30 +- .../java/ca/uhn/fhir/rest/gclient/IPatch.java | 14 +- .../java/ca/uhn/fhir/util/FhirTerser.java | 107 +- .../java/ca/uhn/fhir/util/ParametersUtil.java | 92 +- .../main/java/ca/uhn/fhir/util/XmlUtil.java | 50 +- .../ca/uhn/fhir/i18n/hapi-messages.properties | 6 +- .../fhir/rest/client/impl/GenericClient.java | 10 - .../hapi/fhir/docs/GenericClientExample.java | 1018 ++++++++--------- .../hapi/fhir/docs/client/generic_client.md | 16 - hapi-fhir-jpaserver-base/pom.xml | 5 - .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 25 +- .../java/ca/uhn/fhir/jpa/patch/FhirPatch.java | 316 ----- .../jsonpatch}/JsonPatchUtils.java | 2 +- .../xmlpatch}/XmlPatchUtils.java | 2 +- .../fhir/jpa/patch/BaseFhirPatchCoreTest.java | 115 -- .../fhir/jpa/patch/FhirPatchR4CoreTest.java | 33 - .../uhn/fhir/jpa/patch/FhirPatchR4Test.java | 330 ------ .../fhir/jpa/patch/FhirPatchR5CoreTest.java | 33 - .../jpa/provider/r4/PatchProviderR4Test.java | 35 - .../util/jsonpatch/JsonPatchUtilsTest.java | 1 - .../server/method/PatchTypeParameter.java | 9 - pom.xml | 5 - 23 files changed, 544 insertions(+), 1713 deletions(-) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/{patch => util/jsonpatch}/JsonPatchUtils.java (98%) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/{patch => util/xmlpatch}/XmlPatchUtils.java (97%) delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/BaseFhirPatchCoreTest.java delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4CoreTest.java delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4Test.java delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR5CoreTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java index 9e19c01a896..eb667e765f9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java @@ -164,9 +164,6 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil } if (theClear) { existingList.clear(); - if (theValue == null) { - return; - } } existingList.add(theValue); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java index bd5ef3250ff..4e6d843496e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java @@ -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 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 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; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatch.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatch.java index 4f4690a3929..d87eeb94cf8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatch.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatch.java @@ -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); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index d5a47c3909a..786b92afd43 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -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 Optional getSingleValue(IBase theTarget, String thePath, Class theWantedType) { - return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType)); - } - - private List getValues(BaseRuntimeElementCompositeDefinition theCurrentDef, IBase theCurrentObj, List theSubList, Class 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 getValues(IBase theElement, String thePath) { + public List getValues(IBaseResource theResource, String thePath) { Class 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 thePath The path for the element to be accessed. - * @param theCreate When set to true, the terser will create a null-valued element where none exists. + * @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 true, the terser will create a null-valued element where none exists. * @return A list of values of type {@link Object}. */ - public List getValues(IBase theElement, String thePath, boolean theCreate) { + public List getValues(IBaseResource theResource, String thePath, boolean theCreate) { Class 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 true, the terser will create a null-valued element where none exists. * @param theAddExtension When set to true, 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 getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) { + public List getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) { Class 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 theWantedClass. * - * @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 Type declared by theWantedClass * @return A list of values of type theWantedClass. */ - public List getValues(IBase theElement, String thePath, Class theWantedClass) { - BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theElement.getClass()); + public List getValues(IBaseResource theResource, String thePath, Class theWantedClass) { + RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); List 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 theWantedClass. * - * @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 true, the terser will create a null-valued element where none exists. * @param Type declared by theWantedClass * @return A list of values of type theWantedClass. */ - public List getValues(IBase theElement, String thePath, Class theWantedClass, boolean theCreate) { - BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theElement.getClass()); + public List getValues(IBaseResource theResource, String thePath, Class theWantedClass, boolean theCreate) { + RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); List 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 theWantedClass. * - * @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 true, the terser will create a null-valued element where none exists. @@ -588,10 +557,10 @@ public class FhirTerser { * @param Type declared by theWantedClass * @return A list of values of type theWantedClass. */ - public List getValues(IBase theElement, String thePath, Class theWantedClass, boolean theCreate, boolean theAddExtension) { - BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theElement.getClass()); + public List getValues(IBaseResource theResource, String thePath, Class theWantedClass, boolean theCreate, boolean theAddExtension) { + RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); List parts = parsePath(def, thePath); - return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension); + return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension); } private List 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 *

* THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION *

@@ -841,19 +810,12 @@ public class FhirTerser { * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource) *

* - * @param theElement The element to visit - * @param theVisitor The visitor + * @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 theStack, IBaseResource theResource, IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, @@ -1009,5 +971,4 @@ public class FhirTerser { }); } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index ace55587c7b..a28197931be 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -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; @@ -54,7 +45,7 @@ public class ParametersUtil { } public static List getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { - Function, Integer> mapper = t -> (Integer) t.getValue(); + Function, Integer> mapper = t -> (Integer)t.getValue(); return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); } @@ -62,72 +53,33 @@ public class ParametersUtil { return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst(); } - public static List getNamedParameters(FhirContext theCtx, IBaseResource theParameters, String theParameterName) { + private static List extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function, T> theMapper) { Validate.notNull(theParameters, "theParameters must not be null"); RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass()); BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter"); List parameterReps = parameterChild.getAccessor().getValues(theParameters); - return parameterReps - .stream() - .filter(param -> { - BaseRuntimeElementCompositeDefinition nextParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(param.getClass()); - BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name"); - List nameValues = nameChild.getAccessor().getValues(param); - Optional> 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 getParameterPart(FhirContext theCtx, IBase theParameter, String theParameterName) { - BaseRuntimeElementCompositeDefinition nextParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(theParameter.getClass()); - BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("part"); - List parts = valueChild.getAccessor().getValues(theParameter); - - for (IBase nextPart : parts) { - Optional 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 getParameterPartValue(FhirContext theCtx, IBase theParameter, String theParameterName) { - Optional 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 List extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function, T> theMapper) { List retVal = new ArrayList<>(); - List namedParameters = getNamedParameters(theCtx, theParameters, theParameterName); - for (IBase nextParameter : namedParameters) { + for (IBase nextParameter : parameterReps) { BaseRuntimeElementCompositeDefinition nextParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(nextParameter.getClass()); + BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name"); + List nameValues = nameChild.getAccessor().getValues(nextParameter); + Optional> 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]"); List valueValues = valueChild.getAccessor().getValues(nextParameter); valueValues .stream() .filter(t -> t instanceof IPrimitiveType) - .map(t -> ((IPrimitiveType) t)) + .map(t->((IPrimitiveType) t)) .map(theMapper) .filter(t -> t != null) .forEach(retVal::add); @@ -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 value = (IPrimitiveType) 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 value = (IPrimitiveType) theContext.getElementDefinition("string").newInstance(); value.setValue(theValue); @@ -339,5 +284,4 @@ public class ParametersUtil { partChildElem.getChildByName("resource").getMutator().addValue(part, theValue); } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java index 0ce84915101..b2edb9a22cf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/XmlUtil.java @@ -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(); - } } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 6ae6f2faf34..ac017230a12 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -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} diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index a54e208322f..63e92b431a0 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -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) { diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java index abf4aa75b2c..c9079b7898a 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/GenericClientExample.java @@ -32,321 +32,243 @@ 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; public class GenericClientExample { - public static void deferModelScanning() { - // START SNIPPET: deferModelScanning - // Create a context and configure it for deferred child scanning - FhirContext ctx = FhirContext.forDstu2(); - ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING); + public static void deferModelScanning() { + // START SNIPPET: deferModelScanning + // Create a context and configure it for deferred child scanning + FhirContext ctx = FhirContext.forDstu2(); + ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING); + + // Now create a client and use it + String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; + IGenericClient client = ctx.newRestfulGenericClient(serverBase); + // END SNIPPET: deferModelScanning + } + + public static void performance() { + // START SNIPPET: dontValidate + // Create a context + FhirContext ctx = FhirContext.forDstu2(); - // Now create a client and use it - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - // END SNIPPET: deferModelScanning - } + // Disable server validation (don't pull the server's metadata first) + ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + + // Now create a client and use it + String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; + IGenericClient client = ctx.newRestfulGenericClient(serverBase); + // END SNIPPET: dontValidate + } + + public static void simpleExample() { + // START SNIPPET: simple + // We're connecting to a DSTU1 compliant server in this example + FhirContext ctx = FhirContext.forDstu2(); + String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; + + IGenericClient client = ctx.newRestfulGenericClient(serverBase); - public static void performance() { - // START SNIPPET: dontValidate - // Create a context - FhirContext ctx = FhirContext.forDstu2(); + // Perform a search + Bundle results = client + .search() + .forResource(Patient.class) + .where(Patient.FAMILY.matches().value("duck")) + .returnBundle(Bundle.class) + .execute(); - // Disable server validation (don't pull the server's metadata first) - ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + System.out.println("Found " + results.getEntry().size() + " patients named 'duck'"); + // END SNIPPET: simple + } - // Now create a client and use it - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - // END SNIPPET: dontValidate - } + @SuppressWarnings("unused") + public static void fluentSearch() { + FhirContext ctx = FhirContext.forDstu2(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); + { + // START SNIPPET: create + Patient patient = new Patient(); + // ..populate the patient object.. + patient.addIdentifier().setSystem("urn:system").setValue("12345"); + patient.addName().setFamily("Smith").addGiven("John"); - public static void patchFhir() { - // Create a context - FhirContext ctx = FhirContext.forR4(); + // Invoke the server create method (and send pretty-printed JSON + // encoding to the server + // instead of the default which is non-pretty printed XML) + MethodOutcome outcome = client.create() + .resource(patient) + .prettyPrint() + .encodedJson() + .execute(); + + // The MethodOutcome object will contain information about the + // response from the server, including the ID of the created + // resource, the OperationOutcome response, etc. (assuming that + // any of these things were provided by the server! They may not + // always be) + IIdType id = outcome.getId(); + System.out.println("Got ID: " + id.getValue()); + // END SNIPPET: create + } + { + Patient patient = new Patient(); + // START SNIPPET: createConditional + // One form + MethodOutcome outcome = client.create() + .resource(patient) + .conditionalByUrl("Patient?identifier=system%7C00001") + .execute(); - // START SNIPPET: patchFhir - // Create a client - String serverBase = "http://hapi.fhir.org/baseR4"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); + // Another form + MethodOutcome outcome2 = client.create() + .resource(patient) + .conditional() + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) + .execute(); - // 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]")); + // This will return Boolean.TRUE if the server responded with an HTTP 201 created, + // otherwise it will return null. + Boolean created = outcome.getCreated(); - // Invoke the patch - MethodOutcome outcome = client - .patch() - .withFhirPatch(patch) - .withId("Patient/123") - .execute(); + // The ID of the created, or the pre-existing resource + IIdType id = outcome.getId(); + // END SNIPPET: createConditional + } + { + // START SNIPPET: validate + Patient patient = new Patient(); + patient.addIdentifier().setSystem("http://hospital.com").setValue("123445"); + patient.addName().setFamily("Smith").addGiven("John"); + + // Validate the resource + MethodOutcome outcome = client.validate() + .resource(patient) + .execute(); + + // The returned object will contain an operation outcome resource + OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome(); + + // If the OperationOutcome has any issues with a severity of ERROR or SEVERE, + // the validation failed. + for (OperationOutcome.OperationOutcomeIssueComponent nextIssue : oo.getIssue()) { + if (nextIssue.getSeverity().ordinal() >= OperationOutcome.IssueSeverity.ERROR.ordinal()) { + System.out.println("We failed validation!"); + } + } + // END SNIPPET: validate + } + { + // START SNIPPET: update + Patient patient = new Patient(); + // ..populate the patient object.. + patient.addIdentifier().setSystem("urn:system").setValue("12345"); + patient.addName().setFamily("Smith").addGiven("John"); - // The server may provide the updated contents in the response - Patient resultingResource = (Patient) outcome.getResource(); + // To update a resource, it should have an ID set (if the resource + // object + // comes from the results of a previous read or search, it will already + // have one though) + patient.setId("Patient/123"); - // END SNIPPET: patchFhir - } + // Invoke the server update method + MethodOutcome outcome = client.update() + .resource(patient) + .execute(); - public static void patchJson() { - // Create a context - FhirContext ctx = FhirContext.forR4(); + // The MethodOutcome object will contain information about the + // response from the server, including the ID of the created + // resource, the OperationOutcome response, etc. (assuming that + // any of these things were provided by the server! They may not + // always be) + IdType id = (IdType) outcome.getId(); + System.out.println("Got ID: " + id.getValue()); + // END SNIPPET: update + } + { + Patient patient = new Patient(); + // START SNIPPET: updateConditional + client.update() + .resource(patient) + .conditionalByUrl("Patient?identifier=system%7C00001") + .execute(); - // START SNIPPET: patchJson - // Create a client - String serverBase = "http://hapi.fhir.org/baseR4"; - IGenericClient client = ctx.newRestfulGenericClient(serverBase); + client.update() + .resource(patient) + .conditional() + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) + .execute(); + // END SNIPPET: updateConditional + } + { + // START SNIPPET: etagupdate + // First, let's retrieve the latest version of a resource + // from the server + Patient patient = client.read().resource(Patient.class).withId("123").execute(); - // Create a JSON patch object - String patch = "[ " + - " { " + - " \"op\":\"replace\", " + - " \"path\":\"/active\", " + - " \"value\":false " + - " } " + - "]"; + // If the server is a version aware server, we should now know the latest version + // of the resource + System.out.println("Version ID: " + patient.getIdElement().getVersionIdPart()); + + // Now let's make a change to the resource + patient.setGender(Enumerations.AdministrativeGender.FEMALE); - // 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 - FhirContext ctx = FhirContext.forDstu2(); - String serverBase = "http://fhirtest.uhn.ca/baseDstu2"; - - IGenericClient client = ctx.newRestfulGenericClient(serverBase); - - // Perform a search - Bundle results = client - .search() - .forResource(Patient.class) - .where(Patient.FAMILY.matches().value("duck")) - .returnBundle(Bundle.class) - .execute(); - - System.out.println("Found " + results.getEntry().size() + " patients named 'duck'"); - // END SNIPPET: simple - } - - @SuppressWarnings("unused") - public static void fluentSearch() { - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); - { - // START SNIPPET: create - Patient patient = new Patient(); - // ..populate the patient object.. - patient.addIdentifier().setSystem("urn:system").setValue("12345"); - patient.addName().setFamily("Smith").addGiven("John"); - - // Invoke the server create method (and send pretty-printed JSON - // encoding to the server - // instead of the default which is non-pretty printed XML) - MethodOutcome outcome = client.create() - .resource(patient) - .prettyPrint() - .encodedJson() - .execute(); - - // The MethodOutcome object will contain information about the - // response from the server, including the ID of the created - // resource, the OperationOutcome response, etc. (assuming that - // any of these things were provided by the server! They may not - // always be) - IIdType id = outcome.getId(); - System.out.println("Got ID: " + id.getValue()); - // END SNIPPET: create - } - { - Patient patient = new Patient(); - // START SNIPPET: createConditional - // One form - MethodOutcome outcome = client.create() - .resource(patient) - .conditionalByUrl("Patient?identifier=system%7C00001") - .execute(); - - // Another form - MethodOutcome outcome2 = client.create() - .resource(patient) - .conditional() - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - - // This will return Boolean.TRUE if the server responded with an HTTP 201 created, - // otherwise it will return null. - Boolean created = outcome.getCreated(); - - // The ID of the created, or the pre-existing resource - IIdType id = outcome.getId(); - // END SNIPPET: createConditional - } - { - // START SNIPPET: validate - Patient patient = new Patient(); - patient.addIdentifier().setSystem("http://hospital.com").setValue("123445"); - patient.addName().setFamily("Smith").addGiven("John"); - - // Validate the resource - MethodOutcome outcome = client.validate() - .resource(patient) - .execute(); - - // The returned object will contain an operation outcome resource - OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome(); - - // If the OperationOutcome has any issues with a severity of ERROR or SEVERE, - // the validation failed. - for (OperationOutcome.OperationOutcomeIssueComponent nextIssue : oo.getIssue()) { - if (nextIssue.getSeverity().ordinal() >= OperationOutcome.IssueSeverity.ERROR.ordinal()) { - System.out.println("We failed validation!"); - } - } - // END SNIPPET: validate - } - { - // START SNIPPET: update - Patient patient = new Patient(); - // ..populate the patient object.. - patient.addIdentifier().setSystem("urn:system").setValue("12345"); - patient.addName().setFamily("Smith").addGiven("John"); - - // To update a resource, it should have an ID set (if the resource - // object - // comes from the results of a previous read or search, it will already - // have one though) - patient.setId("Patient/123"); - - // Invoke the server update method - MethodOutcome outcome = client.update() - .resource(patient) - .execute(); - - // The MethodOutcome object will contain information about the - // response from the server, including the ID of the created - // resource, the OperationOutcome response, etc. (assuming that - // any of these things were provided by the server! They may not - // always be) - IdType id = (IdType) outcome.getId(); - System.out.println("Got ID: " + id.getValue()); - // END SNIPPET: update - } - { - Patient patient = new Patient(); - // START SNIPPET: updateConditional - client.update() - .resource(patient) - .conditionalByUrl("Patient?identifier=system%7C00001") - .execute(); - - client.update() - .resource(patient) - .conditional() - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - // END SNIPPET: updateConditional - } - { - // START SNIPPET: etagupdate - // First, let's retrieve the latest version of a resource - // from the server - Patient patient = client.read().resource(Patient.class).withId("123").execute(); - - // If the server is a version aware server, we should now know the latest version - // of the resource - System.out.println("Version ID: " + patient.getIdElement().getVersionIdPart()); - - // Now let's make a change to the resource - patient.setGender(Enumerations.AdministrativeGender.FEMALE); - - // Invoke the server update method - Because the resource has - // a version, it will be included in the request sent to - // the server - try { - MethodOutcome outcome = client - .update() - .resource(patient) - .execute(); - } catch (PreconditionFailedException e) { - // If we get here, the latest version has changed - // on the server so our update failed. - } - // END SNIPPET: etagupdate - } - { - // START SNIPPET: conformance - // Retrieve the server's conformance statement and print its - // description - CapabilityStatement conf = client + // Invoke the server update method - Because the resource has + // a version, it will be included in the request sent to + // the server + try { + MethodOutcome outcome = client + .update() + .resource(patient) + .execute(); + } catch (PreconditionFailedException e) { + // If we get here, the latest version has changed + // on the server so our update failed. + } + // END SNIPPET: etagupdate + } + { + // START SNIPPET: conformance + // Retrieve the server's conformance statement and print its + // description + CapabilityStatement conf = client .capabilities() .ofType(CapabilityStatement.class) .execute(); - System.out.println(conf.getDescriptionElement().getValue()); - // END SNIPPET: conformance - } - { - // START SNIPPET: delete + System.out.println(conf.getDescriptionElement().getValue()); + // END SNIPPET: conformance + } + { + // START SNIPPET: delete MethodOutcome response = client .delete() .resourceById(new IdType("Patient", "1234")) .execute(); - // outcome may be null if the server didn't return one + // outcome may be null if the server didn't return one OperationOutcome outcome = (OperationOutcome) response.getOperationOutcome(); if (outcome != null) { System.out.println(outcome.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); } // END SNIPPET: delete } - { - // START SNIPPET: deleteConditional - client.delete() - .resourceConditionalByUrl("Patient?identifier=system%7C00001") - .execute(); + { + // START SNIPPET: deleteConditional + client.delete() + .resourceConditionalByUrl("Patient?identifier=system%7C00001") + .execute(); - client.delete() - .resourceConditionalByType("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) - .execute(); - // END SNIPPET: deleteConditional - } + client.delete() + .resourceConditionalByType("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001")) + .execute(); + // END SNIPPET: deleteConditional + } { // START SNIPPET: deleteCascade client.delete() @@ -360,292 +282,292 @@ public class GenericClientExample { .execute(); // END SNIPPET: deleteCascade } - { - // START SNIPPET: search - Bundle response = client.search() - .forResource(Patient.class) - .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01")) - .and(Patient.GENERAL_PRACTITIONER.hasChainedProperty(Organization.NAME.matches().value("Smith"))) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: search + { + // START SNIPPET: search + Bundle response = client.search() + .forResource(Patient.class) + .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01")) + .and(Patient.GENERAL_PRACTITIONER.hasChainedProperty(Organization.NAME.matches().value("Smith"))) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: search - // START SNIPPET: searchOr - response = client.search() - .forResource(Patient.class) - .where(Patient.FAMILY.matches().values("Smith", "Smyth")) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchOr + // START SNIPPET: searchOr + response = client.search() + .forResource(Patient.class) + .where(Patient.FAMILY.matches().values("Smith", "Smyth")) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchOr - // START SNIPPET: searchAnd - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .and(Patient.ADDRESS.matches().values("Ontario")) - .and(Patient.ADDRESS.matches().values("Canada")) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchAnd + // START SNIPPET: searchAnd + response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .and(Patient.ADDRESS.matches().values("Ontario")) + .and(Patient.ADDRESS.matches().values("Canada")) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchAnd - // START SNIPPET: searchCompartment - response = client.search() - .forResource(Patient.class) - .withIdAndCompartment("123", "condition") - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchCompartment + // START SNIPPET: searchCompartment + response = client.search() + .forResource(Patient.class) + .withIdAndCompartment("123", "condition") + .where(Patient.ADDRESS.matches().values("Toronto")) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchCompartment - // START SNIPPET: searchUrl - String searchUrl = "http://example.com/base/Patient?identifier=foo"; + // START SNIPPET: searchUrl + String searchUrl = "http://example.com/base/Patient?identifier=foo"; + + // Search URL can also be a relative URL in which case the client's base + // URL will be added to it + searchUrl = "Patient?identifier=foo"; + + response = client.search() + .byUrl(searchUrl) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchUrl - // Search URL can also be a relative URL in which case the client's base - // URL will be added to it - searchUrl = "Patient?identifier=foo"; + // START SNIPPET: searchSubsetSummary + response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .returnBundle(Bundle.class) + .summaryMode(SummaryEnum.TRUE) + .execute(); + // END SNIPPET: searchSubsetSummary - response = client.search() - .byUrl(searchUrl) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchUrl + // START SNIPPET: searchSubsetElements + response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .returnBundle(Bundle.class) + .elementsSubset("identifier", "name") // only include the identifier and name + .execute(); + // END SNIPPET: searchSubsetElements - // START SNIPPET: searchSubsetSummary - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(Bundle.class) - .summaryMode(SummaryEnum.TRUE) - .execute(); - // END SNIPPET: searchSubsetSummary + // START SNIPPET: searchAdv + response = client.search() + .forResource(Patient.class) + .encodedJson() + .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) + .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .withTag("http://acme.org/codes", "needs-review") + .include(Patient.INCLUDE_ORGANIZATION.asRecursive()) + .include(Patient.INCLUDE_GENERAL_PRACTITIONER.asNonRecursive()) + .revInclude(Provenance.INCLUDE_TARGET) + .lastUpdated(new DateRangeParam("2011-01-01", null)) + .sort().ascending(Patient.BIRTHDATE) + .sort().descending(Patient.NAME) + .count(123) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchAdv - // START SNIPPET: searchSubsetElements - response = client.search() - .forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(Bundle.class) - .elementsSubset("identifier", "name") // only include the identifier and name - .execute(); - // END SNIPPET: searchSubsetElements + // START SNIPPET: searchPost + response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("Tester")) + .usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchPost - // START SNIPPET: searchAdv - response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .withTag("http://acme.org/codes", "needs-review") - .include(Patient.INCLUDE_ORGANIZATION.asRecursive()) - .include(Patient.INCLUDE_GENERAL_PRACTITIONER.asNonRecursive()) - .revInclude(Provenance.INCLUDE_TARGET) - .lastUpdated(new DateRangeParam("2011-01-01", null)) - .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME) - .count(123) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchAdv + // START SNIPPET: searchComposite + response = client.search() + .forResource("Observation") + .where(Observation.CODE_VALUE_DATE + .withLeft(Observation.CODE.exactly().code("FOO$BAR")) + .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01"))) + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: searchComposite + } + { + // START SNIPPET: transaction + List resources = new ArrayList(); + // .. populate this list - note that you can also pass in a populated + // Bundle if you want to create one manually .. - // START SNIPPET: searchPost - response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("Tester")) - .usingStyle(SearchStyleEnum.POST) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchPost + List response = client.transaction().withResources(resources).execute(); + // END SNIPPET: transaction + } - // START SNIPPET: searchComposite - response = client.search() - .forResource("Observation") - .where(Observation.CODE_VALUE_DATE - .withLeft(Observation.CODE.exactly().code("FOO$BAR")) - .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01"))) - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: searchComposite - } - { - // START SNIPPET: transaction - List resources = new ArrayList(); - // .. populate this list - note that you can also pass in a populated - // Bundle if you want to create one manually .. + { + // START SNIPPET: read + // search for patient 123 + Patient patient = client.read() + .resource(Patient.class) + .withId("123") + .execute(); + // END SNIPPET: read + } + { + // START SNIPPET: vread + // search for patient 123 (specific version 888) + Patient patient = client.read() + .resource(Patient.class) + .withIdAndVersion("123", "888") + .execute(); + // END SNIPPET: vread + } + { + // START SNIPPET: readabsolute + // search for patient 123 on example.com + String url = "http://example.com/fhir/Patient/123"; + Patient patient = client.read() + .resource(Patient.class) + .withUrl(url) + .execute(); + // END SNIPPET: readabsolute + } + + { + // START SNIPPET: etagread + // search for patient 123 + Patient patient = client.read() + .resource(Patient.class) + .withId("123") + .ifVersionMatches("001").returnNull() + .execute(); + if (patient == null) { + // resource has not changed + } + // END SNIPPET: etagread + } + + + + } - List response = client.transaction().withResources(resources).execute(); - // END SNIPPET: transaction - } - - { - // START SNIPPET: read - // search for patient 123 - Patient patient = client.read() - .resource(Patient.class) - .withId("123") - .execute(); - // END SNIPPET: read - } - { - // START SNIPPET: vread - // search for patient 123 (specific version 888) - Patient patient = client.read() - .resource(Patient.class) - .withIdAndVersion("123", "888") - .execute(); - // END SNIPPET: vread - } - { - // START SNIPPET: readabsolute - // search for patient 123 on example.com - String url = "http://example.com/fhir/Patient/123"; - Patient patient = client.read() - .resource(Patient.class) - .withUrl(url) - .execute(); - // END SNIPPET: readabsolute - } - - { - // START SNIPPET: etagread - // search for patient 123 - Patient patient = client.read() - .resource(Patient.class) - .withId("123") - .ifVersionMatches("001").returnNull() - .execute(); - if (patient == null) { - // resource has not changed - } - // END SNIPPET: etagread - } - - - } - - @SuppressWarnings("unused") - public static void history() { - IGenericClient client = FhirContext.forDstu2().newRestfulGenericClient(""); - { - // START SNIPPET: historyDstu2 + @SuppressWarnings("unused") + public static void history() { + IGenericClient client = FhirContext.forDstu2().newRestfulGenericClient(""); + { + // START SNIPPET: historyDstu2 Bundle response = client - .history() - .onServer() - .returnBundle(Bundle.class) - .execute(); - // END SNIPPET: historyDstu2 - } - { - // START SNIPPET: historyFeatures + .history() + .onServer() + .returnBundle(Bundle.class) + .execute(); + // END SNIPPET: historyDstu2 + } + { + // START SNIPPET: historyFeatures Bundle response = client - .history() - .onServer() - .returnBundle(Bundle.class) - .since(new InstantType("2012-01-01T12:22:32.038Z")) - .count(100) - .execute(); - // END SNIPPET: historyFeatures - } - } + .history() + .onServer() + .returnBundle(Bundle.class) + .since(new InstantType("2012-01-01T12:22:32.038Z")) + .count(100) + .execute(); + // END SNIPPET: historyFeatures + } + } + + public static void main(String[] args) { + paging(); + } + private static void paging() { + { + // START SNIPPET: searchPaging + FhirContext ctx = FhirContext.forDstu2(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); + + // Perform a search + Bundle resultBundle = client.search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("Smith")) + .returnBundle(Bundle.class) + .execute(); + + if (resultBundle.getLink(Bundle.LINK_NEXT) != null) { - public static void main(String[] args) { - paging(); - } + // load next page + Bundle nextPage = client.loadPage().next(resultBundle).execute(); + } + // END SNIPPET: searchPaging + } + } - private static void paging() { - { - // START SNIPPET: searchPaging - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); + @SuppressWarnings("unused") + private static void operationHttpGet() { + // START SNIPPET: operationHttpGet + // Create a client to talk to the HeathIntersections server + FhirContext ctx = FhirContext.forDstu2(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateType("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateType("2015-03-01")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdType("Patient", "1")) + .named("$everything") + .withParameters(inParams) + .useHttpGet() // Use HTTP GET instead of POST + .execute(); + // END SNIPPET: operationHttpGet + } - // Perform a search - Bundle resultBundle = client.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("Smith")) - .returnBundle(Bundle.class) - .execute(); + @SuppressWarnings("unused") + private static void operation() { + // START SNIPPET: operation + // Create a client to talk to the HeathIntersections server + FhirContext ctx = FhirContext.forDstu2(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateType("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateType("2015-03-01")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdType("Patient", "1")) + .named("$everything") + .withParameters(inParams) + .execute(); + + /* + * Note that the $everything operation returns a Bundle instead + * of a Parameters resource. The client operation methods return a + * Parameters instance however, so HAPI creates a Parameters object + * with a single parameter containing the value. + */ + Bundle responseBundle = (Bundle) outParams.getParameter().get(0).getResource(); + + // Print the response bundle + System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(responseBundle)); + // END SNIPPET: operation + } - if (resultBundle.getLink(Bundle.LINK_NEXT) != null) { - - // load next page - Bundle nextPage = client.loadPage().next(resultBundle).execute(); - } - // END SNIPPET: searchPaging - } - } - - @SuppressWarnings("unused") - private static void operationHttpGet() { - // START SNIPPET: operationHttpGet - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateType("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateType("2015-03-01")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdType("Patient", "1")) - .named("$everything") - .withParameters(inParams) - .useHttpGet() // Use HTTP GET instead of POST - .execute(); - // END SNIPPET: operationHttpGet - } - - @SuppressWarnings("unused") - private static void operation() { - // START SNIPPET: operation - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateType("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateType("2015-03-01")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdType("Patient", "1")) - .named("$everything") - .withParameters(inParams) - .execute(); - - /* - * Note that the $everything operation returns a Bundle instead - * of a Parameters resource. The client operation methods return a - * Parameters instance however, so HAPI creates a Parameters object - * with a single parameter containing the value. - */ - Bundle responseBundle = (Bundle) outParams.getParameter().get(0).getResource(); - - // Print the response bundle - System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(responseBundle)); - // END SNIPPET: operation - } - - @SuppressWarnings("unused") - private static void operationNoIn() { - // START SNIPPET: operationNoIn - // Create a client to talk to the HeathIntersections server - FhirContext ctx = FhirContext.forDstu2(); - IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - client.registerInterceptor(new LoggingInterceptor(true)); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdType("Patient", "1")) - .named("$everything") - .withNoParameters(Parameters.class) // No input parameters - .execute(); - // END SNIPPET: operationNoIn - } + @SuppressWarnings("unused") + private static void operationNoIn() { + // START SNIPPET: operationNoIn + // Create a client to talk to the HeathIntersections server + FhirContext ctx = FhirContext.forDstu2(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); + client.registerInterceptor(new LoggingInterceptor(true)); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdType("Patient", "1")) + .named("$everything") + .withNoParameters(Parameters.class) // No input parameters + .execute(); + // END SNIPPET: operationNoIn + } } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md index 353249e9ff3..5cb269efb1d 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md @@ -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. diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 59d4b572847..29555e96b03 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -562,11 +562,6 @@ embedded-elasticsearch 2.10.0 - - org.hl7.fhir.testcases - fhir-test-cases - test - com.github.ben-manes.caffeine diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 8a05171d576..e936542da8d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -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 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") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java deleted file mode 100644 index 7102366fe46..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/FhirPatch.java +++ /dev/null @@ -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 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 valuePart = ParametersUtil.getParameterPart(myContext, nextOp, "value"); - Optional 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) 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) t) - .map(t -> t.getValue()) - .orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation")); - removeIndex = ParametersUtil - .getParameterPartValue(myContext, nextOp, "source") - .map(t -> (IPrimitiveType) t) - .map(t -> t.getValue()) - .orElseThrow(() -> new InvalidRequestException("No index supplied for insert operation")); - - } else { - - throw new InvalidRequestException("Unknown patch operation type: " + type); - - } - - List 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 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 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 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 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 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 theContainingElementPath, List theChildDefinitionPath, List> theElementDefinitionPath) { - if (theElement instanceof IPrimitiveType) { - ((IPrimitiveType) theElement).setValueAsString(null); - } - return true; - } - - @Override - public boolean acceptUndeclaredExtension(IBaseExtension theNextExt, List theContainingElementPath, List theChildDefinitionPath, List> 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 sourceValues = nextChild.getAccessor().getValues(theOldField); - List 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 () - - - } - - } - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/JsonPatchUtils.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/JsonPatchUtils.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java index 51ffa794016..516f7eb617a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/JsonPatchUtils.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.patch; +package ca.uhn.fhir.jpa.util.jsonpatch; /* * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/XmlPatchUtils.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/XmlPatchUtils.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java index b8ae5b28d61..1716f0976d1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/patch/XmlPatchUtils.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/xmlpatch/XmlPatchUtils.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/BaseFhirPatchCoreTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/BaseFhirPatchCoreTest.java deleted file mode 100644 index e7ee5f14f27..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/BaseFhirPatchCoreTest.java +++ /dev/null @@ -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 loadTestSpec(FhirContext theContext, String theTestSpec) throws IOException, SAXException, TransformerException { - List 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(); - } -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4CoreTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4CoreTest.java deleted file mode 100644 index 419b263fce7..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4CoreTest.java +++ /dev/null @@ -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 parameters() throws IOException, SAXException, TransformerException { - String testSpec = "/org/hl7/fhir/testcases/r4/patch/fhir-path-tests.xml"; - return loadTestSpec(ourCtx, testSpec); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4Test.java deleted file mode 100644 index bf86a393702..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR4Test.java +++ /dev/null @@ -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 extractPartValue(Parameters theDiff, int theIndex, String theParameterName, String thePartName, Class 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(); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR5CoreTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR5CoreTest.java deleted file mode 100644 index 67dfbdb643d..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/patch/FhirPatchR5CoreTest.java +++ /dev/null @@ -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 parameters() throws IOException, SAXException, TransformerException { - String testSpec = "/org/hl7/fhir/testcases/r5/patch/fhir-path-tests.xml"; - return loadTestSpec(ourCtx, testSpec); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java index d8f72d34686..60c65528c52 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtilsTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtilsTest.java index 944ecfe8a43..749b39dde90 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtilsTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtilsTest.java @@ -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; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java index 363efb05c30..45e087b7582 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java @@ -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); } diff --git a/pom.xml b/pom.xml index ff7befaf766..593c4c067f5 100644 --- a/pom.xml +++ b/pom.xml @@ -870,11 +870,6 @@ commons-csv 1.7 - - org.hl7.fhir.testcases - fhir-test-cases - 1.1.14-SNAPSHOT - org.jetbrains annotations