More work on patch
This commit is contained in:
parent
ad447126f2
commit
6e97936eca
|
@ -29,69 +29,23 @@ import java.io.PushbackReader;
|
|||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype;
|
||||
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.IBaseIntegerDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IDomainResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.INarrative;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
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.RuntimeChildContainedResources;
|
||||
import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.model.api.BaseBundle;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.BundleEntry;
|
||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.api.Tag;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
||||
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
|
||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.narrative.INarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
|
@ -170,6 +124,16 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) {
|
||||
theListToAddTo.ensureCapacity(theValueIdx);
|
||||
while (theListToAddTo.size() <= theValueIdx) {
|
||||
theListToAddTo.add(null);
|
||||
}
|
||||
if (theListToAddTo.get(theValueIdx) == null) {
|
||||
theListToAddTo.set(theValueIdx, theId);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertObjectOfType(JsonElement theResourceTypeObj, Object theValueType, String thePosition) {
|
||||
// if (theResourceTypeObj == null) {
|
||||
// throw new DataFormatException("Invalid JSON content detected, missing required element: '" + thePosition + "'");
|
||||
|
@ -180,6 +144,16 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
// }
|
||||
}
|
||||
|
||||
private void beginArray(JsonWriter theEventWriter, String arrayName) throws IOException {
|
||||
theEventWriter.name(arrayName);
|
||||
theEventWriter.beginArray();
|
||||
}
|
||||
|
||||
private void beginObject(JsonWriter theEventWriter, String arrayName) throws IOException {
|
||||
theEventWriter.name(arrayName);
|
||||
theEventWriter.beginObject();
|
||||
}
|
||||
|
||||
private JsonWriter createJsonWriter(Writer theWriter) {
|
||||
JsonWriter retVal = new JsonWriter(theWriter);
|
||||
retVal.setSerializeNulls(true);
|
||||
|
@ -234,37 +208,6 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private JsonObject parse(Reader theReader) {
|
||||
|
||||
PushbackReader pbr = new PushbackReader(theReader);
|
||||
JsonObject object;
|
||||
try {
|
||||
while(true) {
|
||||
int nextInt;
|
||||
nextInt = pbr.read();
|
||||
if (nextInt == -1) {
|
||||
throw new DataFormatException("Did not find any content to parse");
|
||||
}
|
||||
if (nextInt == '{') {
|
||||
pbr.unread('{');
|
||||
break;
|
||||
}
|
||||
if (Character.isWhitespace(nextInt)) {
|
||||
continue;
|
||||
}
|
||||
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
|
||||
}
|
||||
|
||||
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
|
||||
|
||||
object = gson.fromJson(pbr, JsonObject.class);
|
||||
} catch (Exception e) {
|
||||
throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private void encodeBundleToWriterInDstu1Format(Bundle theBundle, JsonWriter theEventWriter) throws IOException {
|
||||
theEventWriter.beginObject();
|
||||
|
||||
|
@ -560,23 +503,6 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
|
||||
}
|
||||
|
||||
private void write(JsonWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
|
||||
if (theValue != null) {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theValue.booleanValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void write(JsonWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theDecimalValue);
|
||||
}
|
||||
|
||||
private void write(JsonWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theValue);
|
||||
}
|
||||
|
||||
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent) throws IOException {
|
||||
|
||||
{
|
||||
|
@ -796,16 +722,6 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) {
|
||||
theListToAddTo.ensureCapacity(theValueIdx);
|
||||
while (theListToAddTo.size() <= theValueIdx) {
|
||||
theListToAddTo.add(null);
|
||||
}
|
||||
if (theListToAddTo.get(theValueIdx) == null) {
|
||||
theListToAddTo.set(theValueIdx, theId);
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent) throws IOException, DataFormatException {
|
||||
|
||||
writeCommentsPreAndPost(theNextValue, theEventWriter);
|
||||
|
@ -1083,6 +999,37 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
return (JsonArray) object;
|
||||
}
|
||||
|
||||
private JsonObject parse(Reader theReader) {
|
||||
|
||||
PushbackReader pbr = new PushbackReader(theReader);
|
||||
JsonObject object;
|
||||
try {
|
||||
while(true) {
|
||||
int nextInt;
|
||||
nextInt = pbr.read();
|
||||
if (nextInt == -1) {
|
||||
throw new DataFormatException("Did not find any content to parse");
|
||||
}
|
||||
if (nextInt == '{') {
|
||||
pbr.unread('{');
|
||||
break;
|
||||
}
|
||||
if (Character.isWhitespace(nextInt)) {
|
||||
continue;
|
||||
}
|
||||
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
|
||||
}
|
||||
|
||||
Gson gson = newGson();
|
||||
|
||||
object = gson.fromJson(pbr, JsonObject.class);
|
||||
} catch (Exception e) {
|
||||
throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private void parseAlternates(JsonElement theAlternateVal, ParserState<?> theState, String theElementName) {
|
||||
if (theAlternateVal == null || theAlternateVal instanceof JsonNull) {
|
||||
return;
|
||||
|
@ -1404,6 +1351,17 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
return state.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setPrettyPrint(boolean thePrettyPrint) {
|
||||
myPrettyPrint = thePrettyPrint;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void write(JsonWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theDecimalValue);
|
||||
}
|
||||
|
||||
// private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String
|
||||
// theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) {
|
||||
// String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl);
|
||||
|
@ -1430,10 +1388,16 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
// theState.endingElement();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public IParser setPrettyPrint(boolean thePrettyPrint) {
|
||||
myPrettyPrint = thePrettyPrint;
|
||||
return this;
|
||||
private void write(JsonWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
|
||||
if (theValue != null) {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theValue.booleanValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void write(JsonWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
|
||||
theEventWriter.name(theChildName);
|
||||
theEventWriter.value(theValue);
|
||||
}
|
||||
|
||||
private boolean writeAtomLinkInDstu1Format(JsonWriter theEventWriter, String theRel, StringDt theLink, boolean theStarted) throws IOException {
|
||||
|
@ -1481,16 +1445,6 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void beginArray(JsonWriter theEventWriter, String arrayName) throws IOException {
|
||||
theEventWriter.name(arrayName);
|
||||
theEventWriter.beginArray();
|
||||
}
|
||||
|
||||
private void beginObject(JsonWriter theEventWriter, String arrayName) throws IOException {
|
||||
theEventWriter.name(arrayName);
|
||||
theEventWriter.beginObject();
|
||||
}
|
||||
|
||||
private void writeCategories(JsonWriter theEventWriter, TagList categories) throws IOException {
|
||||
if (categories != null && categories.size() > 0) {
|
||||
theEventWriter.name("category");
|
||||
|
@ -1586,13 +1540,23 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
// }
|
||||
}
|
||||
|
||||
public static Gson newGson() {
|
||||
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
|
||||
return gson;
|
||||
}
|
||||
|
||||
private static void write(JsonWriter theWriter, String theName, String theValue) throws IOException {
|
||||
theWriter.name(theName);
|
||||
theWriter.value(theValue);
|
||||
}
|
||||
|
||||
private class HeldExtension implements Comparable<HeldExtension> {
|
||||
|
||||
private CompositeChildElement myChildElem;
|
||||
private RuntimeChildDeclaredExtensionDefinition myDef;
|
||||
private boolean myModifier;
|
||||
private IBaseExtension<?, ?> myUndeclaredExtension;
|
||||
private IBase myValue;
|
||||
private CompositeChildElement myChildElem;
|
||||
|
||||
public HeldExtension(IBaseExtension<?, ?> theUndeclaredExtension, boolean theModifier, CompositeChildElement theChildElem) {
|
||||
assert theUndeclaredExtension != null;
|
||||
|
@ -1697,10 +1661,5 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private static void write(JsonWriter theWriter, String theName, String theValue) throws IOException {
|
||||
theWriter.name(theName);
|
||||
theWriter.value(theValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2359,9 +2359,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
BaseHttpClientInvocation invocation;
|
||||
if (mySearchUrl != null) {
|
||||
invocation = MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, mySearchUrl);
|
||||
invocation = null;//MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, mySearchUrl);
|
||||
} else if (myCriterionList != null) {
|
||||
invocation = MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList());
|
||||
invocation = null;//MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList());
|
||||
} else {
|
||||
if (myId == null) {
|
||||
myId = myResource.getIdElement();
|
||||
|
|
|
@ -402,7 +402,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
|||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @"
|
||||
+ GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
|
||||
}
|
||||
} else if (MethodOutcome.class.equals(returnTypeFromMethod)) {
|
||||
} else if (MethodOutcome.class.isAssignableFrom(returnTypeFromMethod)) {
|
||||
// returns a method outcome
|
||||
} else if (IBundleProvider.class.equals(returnTypeFromMethod)) {
|
||||
// returns a bundle provider
|
||||
|
|
|
@ -22,7 +22,7 @@ public abstract class BaseOutcomeReturningMethodBindingWithResourceIdButNoResour
|
|||
super(theMethod, theContext, theMethodAnnotationType, theProvider);
|
||||
|
||||
Class<? extends IBaseResource> resourceType = theResourceTypeFromAnnotation;
|
||||
if (resourceType != IResource.class) {
|
||||
if (resourceType != IBaseResource.class) {
|
||||
RuntimeResourceDefinition def = theContext.getResourceDefinition(resourceType);
|
||||
myResourceName = def.getName();
|
||||
} else {
|
||||
|
|
|
@ -99,7 +99,18 @@
|
|||
<groupId>org.jscience</groupId>
|
||||
<artifactId>jscience</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Patch Dependencies -->
|
||||
<dependency>
|
||||
<groupId>net.riotopsys</groupId>
|
||||
<artifactId>json_patch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.dnault</groupId>
|
||||
<artifactId>xml-patch</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- FHIR RI is pulled in for UCUM support, but we don't want any of its dependencies. -->
|
||||
<!-- <dependency> <groupId>me.fhir</groupId> <artifactId>fhir-dstu1</artifactId> <version>0.0.81.2489</version> <exclusions> <exclusion> <artifactId>Saxon-HE</artifactId> <groupId>net.sf.saxon</groupId>
|
||||
</exclusion> <exclusion> <artifactId>commons-discovery</artifactId> <groupId>commons-discovery</groupId> </exclusion> <exclusion> <artifactId>commons-codec</artifactId> <groupId>commons-codec</groupId>
|
||||
|
|
|
@ -22,28 +22,15 @@ package ca.uhn.fhir.jpa.dao;
|
|||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
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.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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
@ -57,33 +44,21 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
|
|||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
||||
import ca.uhn.fhir.jpa.entity.BaseTag;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
|
||||
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
@ -1100,6 +1075,29 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return outcome;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails) {
|
||||
ResourceTable entityToUpdate = readEntityLatestVersion(theId);
|
||||
if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
|
||||
throw new PreconditionFailedException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
|
||||
}
|
||||
|
||||
validateResourceType(entityToUpdate);
|
||||
|
||||
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
|
||||
IBaseResource destination;
|
||||
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
|
||||
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
} else {
|
||||
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T destinationCasted = (T) destination;
|
||||
return update(destinationCasted, null, true, theRequestDetails);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, true, theRequestDetails);
|
||||
|
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
|
|||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.ValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
@ -210,6 +211,8 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
*/
|
||||
MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequestDetails);
|
||||
|
||||
DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails);
|
||||
|
||||
// /**
|
||||
// * Invoke the everything operation
|
||||
// */
|
||||
|
|
|
@ -28,14 +28,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.rest.annotation.At;
|
||||
import ca.uhn.fhir.rest.annotation.GetTags;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
|
@ -128,6 +125,16 @@ public abstract class BaseJpaResourceProvider<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
@Patch
|
||||
public DaoMethodOutcome patch(HttpServletRequest theRequest, @IdParam IIdType theId, RequestDetails theRequestDetails, @ResourceParam String theBody, PatchTypeEnum thePatchType) {
|
||||
startRequest(theRequest);
|
||||
try {
|
||||
return myDao.patch(theId, thePatchType, theBody, theRequestDetails);
|
||||
} finally {
|
||||
endRequest(theRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@Required
|
||||
public void setDao(IFhirResourceDao<T> theDao) {
|
||||
myDao = theDao;
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import net.riotopsys.json_patch.JsonPath;
|
||||
import net.riotopsys.json_patch.operation.AbsOperation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class CopyOperation extends AbsOperation {
|
||||
|
||||
public JsonPath mySourcePath;
|
||||
|
||||
public CopyOperation(JsonPath theTargetPath, JsonPath theSourcePath) {
|
||||
mySourcePath = theSourcePath;
|
||||
this.path = theTargetPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOperationName() {
|
||||
return "copy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement apply(JsonElement original) {
|
||||
JsonElement result = duplicate(original);
|
||||
|
||||
JsonElement item = path.head().navigate(result);
|
||||
JsonElement data = mySourcePath.head().navigate(original);
|
||||
|
||||
if (item.isJsonObject()) {
|
||||
item.getAsJsonObject().add(path.tail(), data);
|
||||
} else if (item.isJsonArray()) {
|
||||
|
||||
JsonArray array = item.getAsJsonArray();
|
||||
|
||||
int index = (path.tail().equals("-")) ? array.size() : Integer.valueOf(path.tail());
|
||||
|
||||
List<JsonElement> temp = new ArrayList<JsonElement>();
|
||||
|
||||
Iterator<JsonElement> iter = array.iterator();
|
||||
while (iter.hasNext()) {
|
||||
JsonElement stuff = iter.next();
|
||||
iter.remove();
|
||||
temp.add(stuff);
|
||||
}
|
||||
|
||||
temp.add(index, data);
|
||||
|
||||
for (JsonElement stuff : temp) {
|
||||
array.add(stuff);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.JsonParser;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import net.riotopsys.json_patch.JsonPatch;
|
||||
import net.riotopsys.json_patch.JsonPath;
|
||||
import net.riotopsys.json_patch.operation.AddOperation;
|
||||
import net.riotopsys.json_patch.operation.MoveOperation;
|
||||
import net.riotopsys.json_patch.operation.RemoveOperation;
|
||||
import net.riotopsys.json_patch.operation.ReplaceOperation;
|
||||
|
||||
public class JsonPatchUtils {
|
||||
|
||||
public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) {
|
||||
JsonPatch parsedPatch = new JsonPatch();
|
||||
|
||||
// Parse the patch
|
||||
Gson gson = JsonParser.newGson();
|
||||
JsonElement jsonElement = gson.fromJson(thePatchBody, JsonElement.class);
|
||||
JsonArray array = jsonElement.getAsJsonArray();
|
||||
for (JsonElement nextElement : array) {
|
||||
JsonObject nextElementAsObject = (JsonObject) nextElement;
|
||||
|
||||
String opName = nextElementAsObject.get("op").getAsString();
|
||||
if ("add".equals(opName)) {
|
||||
AddOperation op = new AddOperation(toPath(nextElementAsObject), nextElementAsObject.get("value"));
|
||||
parsedPatch.add(op);
|
||||
} else if ("remove".equals(opName)) {
|
||||
RemoveOperation op = new RemoveOperation(toPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else if ("replace".equals(opName)) {
|
||||
ReplaceOperation op = new ReplaceOperation(toPath(nextElementAsObject), nextElementAsObject.get("value"));
|
||||
parsedPatch.add(op);
|
||||
} else if ("copy".equals(opName)) {
|
||||
CopyOperation op = new CopyOperation(toPath(nextElementAsObject), toFromPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else if ("move".equals(opName)) {
|
||||
MoveOperation op = new MoveOperation(toPath(nextElementAsObject), toFromPath(nextElementAsObject));
|
||||
parsedPatch.add(op);
|
||||
} else {
|
||||
throw new InvalidRequestException("Invalid JSON PATCH operation: " + opName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
|
||||
|
||||
JsonElement originalJsonDocument = gson.fromJson(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate), JsonElement.class);
|
||||
JsonElement target = parsedPatch.apply(originalJsonDocument);
|
||||
T retVal = theCtx.newJsonParser().parseResource(clazz, gson.toJson(target));
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static JsonPath toFromPath(JsonObject nextElementAsObject) {
|
||||
return new JsonPath(nextElementAsObject.get("from").getAsString());
|
||||
}
|
||||
|
||||
private static JsonPath toPath(JsonObject nextElementAsObject) {
|
||||
return new JsonPath(nextElementAsObject.get("path").getAsString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.jpa.util.xmlpatch;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import com.github.dnault.xmlpatch.Patcher;
|
||||
import com.phloc.commons.io.streams.StringInputStream;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
||||
public class XmlPatchUtils {
|
||||
|
||||
public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
|
||||
|
||||
String inputResource = theCtx.newXmlParser().encodeResourceToString(theResourceToUpdate);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try {
|
||||
Patcher.patch(new StringInputStream(inputResource, StandardCharsets.UTF_8), new StringInputStream(thePatchBody, StandardCharsets.UTF_8), result);
|
||||
} catch (IOException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
String resultString = new String(result.toByteArray(), StandardCharsets.UTF_8);
|
||||
T retVal = theCtx.newXmlParser().parseResource(clazz, resultString);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -36,11 +36,7 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.dstu3.model.AuditEvent;
|
||||
|
@ -130,6 +126,8 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchPagingKeepsOldSearches() throws Exception {
|
||||
|
@ -171,7 +169,67 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingJsonPatch() throws Exception {
|
||||
String methodName = "testPatchUsingJsonPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().addFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
|
||||
patch.setEntity(new StringEntity("[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]", ContentType.parse(Constants.CT_JSON_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchUsingXmlPatch() throws Exception {
|
||||
String methodName = "testPatchUsingXmlPatch";
|
||||
IIdType pid1;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||
patient.addName().addFamily(methodName).addGiven("Joe");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
HttpPatch patch = new HttpPatch(ourServerBase + "/Patient/" + pid1.getIdPart());
|
||||
String patchString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><diff xmlns:fhir=\"http://hl7.org/fhir\"><replace sel=\"fhir:Patient/fhir:active/@value\">false</replace></diff>";
|
||||
patch.setEntity(new StringEntity(patchString, ContentType.parse(Constants.CT_XML_PATCH + Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||
|
||||
CloseableHttpResponse response = ourHttpClient.execute(patch);
|
||||
try {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(),StandardCharsets.UTF_8);
|
||||
assertThat(responseString, containsString("<OperationOutcome"));
|
||||
assertThat(responseString, containsString("INFORMATION"));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||
assertEquals(false, newPt.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasParameter() throws Exception {
|
||||
IIdType pid0;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package ca.uhn.fhir.jpa.util.jsonpatch;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class JsonPatchTest {
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
|
||||
@Test
|
||||
public void testPatchReplace() {
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
|
||||
//@formatter:off
|
||||
String patchBody = "[\n" +
|
||||
" { \"op\":\"replace\", \"path\":\"/active\", \"value\":false }\n" +
|
||||
"]";
|
||||
//@formatter:on
|
||||
|
||||
Patient dest = JsonPatchUtils.apply(ourCtx, p, patchBody);
|
||||
assertEquals(false, dest.getActive());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.jpa.util.xmlpatch;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class XmlPatchTest {
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
|
||||
@Test
|
||||
public void testPatchReplace() {
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
|
||||
//@formatter:off
|
||||
String patchBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><diff xmlns:fhir=\"http://hl7.org/fhir\"><replace sel=\"fhir:Patient/fhir:active/@value\">false</replace></diff>";
|
||||
//@formatter:on
|
||||
|
||||
Patient dest = XmlPatchUtils.apply(ourCtx, p, patchBody);
|
||||
assertEquals(false, dest.getActive());
|
||||
}
|
||||
|
||||
}
|
16
pom.xml
16
pom.xml
|
@ -40,6 +40,17 @@
|
|||
<developerConnection>scm:git:git@github.com:jamesagnew/hapi-fhir.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>bintray-dnault-maven</id>
|
||||
<name>bintray</name>
|
||||
<url>http://dl.bintray.com/dnault/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<description>
|
||||
</description>
|
||||
|
||||
|
@ -304,6 +315,11 @@
|
|||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.dnault</groupId>
|
||||
<artifactId>xml-patch</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
Loading…
Reference in New Issue