diff --git a/hapi-fhir-base/.classpath b/hapi-fhir-base/.classpath
index 3e3200180cf..d0b602b45e1 100644
--- a/hapi-fhir-base/.classpath
+++ b/hapi-fhir-base/.classpath
@@ -30,7 +30,7 @@
-
+
diff --git a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
index 5e625f15742..201420a2537 100644
--- a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
+++ b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
@@ -6,13 +6,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annota
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java
index 895e795a5f1..d9c4f2382b7 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java
@@ -107,6 +107,11 @@ import ca.uhn.fhir.rest.gclient.IOperationUntyped;
import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput;
import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput;
import ca.uhn.fhir.rest.gclient.IParam;
+import ca.uhn.fhir.rest.gclient.IPatch;
+import ca.uhn.fhir.rest.gclient.IPatchExecutable;
+import ca.uhn.fhir.rest.gclient.IPatchTyped;
+import ca.uhn.fhir.rest.gclient.IPatchWithQuery;
+import ca.uhn.fhir.rest.gclient.IPatchWithQueryTyped;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.IRead;
import ca.uhn.fhir.rest.gclient.IReadExecutable;
@@ -519,6 +524,32 @@ public class GenericClient extends BaseClient implements IGenericClient {
return new ArrayList(resp.toListOfResources());
}
+
+
+ @Override
+ public IPatch patch() {
+ return new PatchInternal();
+ }
+
+ @Override
+ public MethodOutcome patch(IdDt theIdDt, IBaseResource theResource) {
+ BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext);
+ if (isKeepResponses()) {
+ myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
+ }
+
+ RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
+ final String resourceName = def.getName();
+
+ OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
+ MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
+ return resp;
+ }
+
+ @Override
+ public MethodOutcome patch(String theId, IBaseResource theResource) {
+ return update(new IdDt(theId), theResource);
+ }
@Override
public IUpdate update() {
@@ -2287,6 +2318,124 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
+
+ private class PatchInternal extends BaseClientExecutable implements IPatch, IPatchTyped, IPatchExecutable, IPatchWithQuery, IPatchWithQueryTyped {
+
+ private CriterionList myCriterionList;
+ private IIdType myId;
+ private PreferReturnEnum myPrefer;
+ private IBaseResource myResource;
+ private String myResourceBody;
+ private String mySearchUrl;
+
+ @Override
+ public IPatchWithQueryTyped and(ICriterion> theCriterion) {
+ myCriterionList.add((ICriterionInternal) theCriterion);
+ return this;
+ }
+
+ @Override
+ public IPatchWithQuery conditional() {
+ myCriterionList = new CriterionList();
+ return this;
+ }
+
+ @Override
+ public IPatchTyped conditionalByUrl(String theSearchUrl) {
+ mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
+ return this;
+ }
+
+ @Override
+ public MethodOutcome execute() {
+ if (myResource == null) {
+ myResource = parseResourceBody(myResourceBody);
+ }
+
+ // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
+ if (getParamEncoding() != null) {
+ myResourceBody = null;
+ }
+
+ BaseHttpClientInvocation invocation;
+ if (mySearchUrl != null) {
+ invocation = MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, mySearchUrl);
+ } else if (myCriterionList != null) {
+ invocation = MethodUtil.createPatchInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList());
+ } else {
+ if (myId == null) {
+ myId = myResource.getIdElement();
+ }
+
+ if (myId == null || myId.hasIdPart() == false) {
+ throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server");
+ }
+ invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
+ }
+
+ addPreferHeader(myPrefer, invocation);
+
+ RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
+ final String resourceName = def.getName();
+
+ OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName, myPrefer);
+
+ Map> params = new HashMap>();
+ return invoke(params, binding, invocation);
+
+ }
+
+ @Override
+ public IPatchExecutable prefer(PreferReturnEnum theReturn) {
+ myPrefer = theReturn;
+ return this;
+ }
+
+ @Override
+ public IPatchTyped resource(IBaseResource theResource) {
+ Validate.notNull(theResource, "Resource can not be null");
+ myResource = theResource;
+ return this;
+ }
+
+ @Override
+ public IPatchTyped resource(String theResourceBody) {
+ Validate.notBlank(theResourceBody, "Body can not be null or blank");
+ myResourceBody = theResourceBody;
+ return this;
+ }
+
+ @Override
+ public IPatchWithQueryTyped where(ICriterion> theCriterion) {
+ myCriterionList.add((ICriterionInternal) theCriterion);
+ return this;
+ }
+
+ @Override
+ public IPatchExecutable withId(IIdType theId) {
+ if (theId == null) {
+ throw new NullPointerException("theId can not be null");
+ }
+ if (theId.hasIdPart() == false) {
+ throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue());
+ }
+ myId = theId;
+ return this;
+ }
+
+ @Override
+ public IPatchExecutable withId(String theId) {
+ if (theId == null) {
+ throw new NullPointerException("theId can not be null");
+ }
+ if (isBlank(theId)) {
+ throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId);
+ }
+ myId = new IdDt(theId);
+ return this;
+ }
+
+ }
private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
index ac49faf4b8d..7d79af36e3e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
@@ -46,6 +46,7 @@ import ca.uhn.fhir.rest.gclient.IGetTags;
import ca.uhn.fhir.rest.gclient.IHistory;
import ca.uhn.fhir.rest.gclient.IMeta;
import ca.uhn.fhir.rest.gclient.IOperation;
+import ca.uhn.fhir.rest.gclient.IPatch;
import ca.uhn.fhir.rest.gclient.IRead;
import ca.uhn.fhir.rest.gclient.ITransaction;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
@@ -253,6 +254,35 @@ public interface IGenericClient extends IRestfulClient {
@Override
void registerInterceptor(IClientInterceptor theInterceptor);
+
+ /**
+ * Fluent method for the "patch" operation, which performs a logical patch on a server resource
+ */
+ IPatch patch();
+
+ /**
+ * Implementation of the "instance patch" method.
+ *
+ * @param theId
+ * The ID to update
+ * @param theResource
+ * The new resource body
+ * @return An outcome containing the results and possibly the new version ID
+ */
+ MethodOutcome patch(IdDt theId, IBaseResource theResource);
+
+ /**
+ * Implementation of the "instance update" method.
+ *
+ * @param theId
+ * The ID to update
+ * @param theResource
+ * The new resource body
+ * @return An outcome containing the results and possibly the new version ID
+ */
+ MethodOutcome patch(String theId, IBaseResource theResource);
+
+
/**
* Search for resources matching a given set of criteria. Searching is a very powerful
* feature in FHIR with many features for specifying exactly what should be seaerched for
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
new file mode 100644
index 00000000000..7fc26ed0d7f
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatch.java
@@ -0,0 +1,31 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2016 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+public interface IPatch {
+
+ IPatchTyped resource(IBaseResource theResource);
+
+ IPatchTyped resource(String theResourceBody);
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchExecutable.java
new file mode 100644
index 00000000000..3fb718598f3
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchExecutable.java
@@ -0,0 +1,37 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2016 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PreferReturnEnum;
+
+public interface IPatchExecutable extends IClientExecutable{
+
+ /**
+ * Add a Prefer
header to the request, which requests that the server include
+ * or suppress the resource body as a part of the result. If a resource is returned by the server
+ * it will be parsed an accessible to the client via {@link MethodOutcome#getResource()}
+ *
+ * @since HAPI 1.1
+ */
+ IPatchExecutable prefer(PreferReturnEnum theReturn);
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchTyped.java
new file mode 100644
index 00000000000..edd47a09a71
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchTyped.java
@@ -0,0 +1,46 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2016 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.hl7.fhir.instance.model.api.IIdType;
+
+public interface IPatchTyped extends IPatchExecutable {
+
+ IPatchExecutable withId(IIdType theId);
+
+ IPatchExecutable withId(String theId);
+
+ /**
+ * Specifies that the update should be performed as a conditional create
+ * against a given search URL.
+ *
+ * @param theSearchUrl The search URL to use. The format of this URL should be of the form [ResourceType]?[Parameters]
,
+ * for example: Patient?name=Smith&identifier=13.2.4.11.4%7C847366
+ * @since HAPI 0.9 / FHIR DSTU 2
+ */
+ IPatchTyped conditionalByUrl(String theSearchUrl);
+
+ /**
+ * @since HAPI 0.9 / FHIR DSTU 2
+ */
+ IPatchWithQuery conditional();
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQuery.java
new file mode 100644
index 00000000000..32b94c5c02c
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQuery.java
@@ -0,0 +1,26 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2016 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+
+public interface IPatchWithQuery extends IBaseQuery {
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQueryTyped.java
new file mode 100644
index 00000000000..cdc639f1dfa
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithQueryTyped.java
@@ -0,0 +1,25 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2016 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public interface IPatchWithQueryTyped extends IPatchTyped, IPatchWithQuery {
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
index ae040610e87..3db12c7f2a0 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
@@ -155,6 +155,90 @@ public class MethodUtil {
retVal.setIfNoneExistString(theIfNoneExistUrl);
return retVal;
}
+
+ /** Patch **/
+
+ public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map> theMatchParams) {
+ StringBuilder b = new StringBuilder();
+
+ String resourceType = theContext.getResourceDefinition(theResource).getName();
+ b.append(resourceType);
+
+ boolean haveQuestionMark = false;
+ for (Entry> nextEntry : theMatchParams.entrySet()) {
+ for (String nextValue : nextEntry.getValue()) {
+ b.append(haveQuestionMark ? '&' : '?');
+ haveQuestionMark = true;
+ b.append(UrlUtil.escape(nextEntry.getKey()));
+ b.append('=');
+ b.append(UrlUtil.escape(nextValue));
+ }
+ }
+
+ HttpPatchClientInvocation retVal;
+ if (StringUtils.isBlank(theResourceBody)) {
+ retVal = new HttpPatchClientInvocation(theContext, theResource, b.toString());
+ } else {
+ retVal = new HttpPatchClientInvocation(theContext, theResourceBody, false, b.toString());
+ }
+
+ addTagsToPostOrPut(theContext, theResource, retVal);
+
+ return retVal;
+ }
+
+
+ public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) {
+ HttpPatchClientInvocation retVal;
+ if (StringUtils.isBlank(theResourceBody)) {
+ retVal = new HttpPatchClientInvocation(theContext, theResource, theMatchUrl);
+ } else {
+ retVal = new HttpPatchClientInvocation(theContext, theResourceBody, false, theMatchUrl);
+ }
+
+ addTagsToPostOrPut(theContext, theResource, retVal);
+
+ return retVal;
+ }
+
+ public static HttpPatchClientInvocation createPatchInvocation(IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) {
+ String resourceName = theContext.getResourceDefinition(theResource).getName();
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(resourceName);
+ urlBuilder.append('/');
+ urlBuilder.append(theId.getIdPart());
+ String urlExtension = urlBuilder.toString();
+
+ HttpPatchClientInvocation retVal;
+ if (StringUtils.isBlank(theResourceBody)) {
+ retVal = new HttpPatchClientInvocation(theContext, theResource, urlExtension);
+ } else {
+ retVal = new HttpPatchClientInvocation(theContext, theResourceBody, false, urlExtension);
+ }
+
+ retVal.setForceResourceId(theId);
+
+ if (theId.hasVersionIdPart()) {
+ if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
+ retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"');
+ } else {
+ String versionId = theId.getVersionIdPart();
+ if (StringUtils.isNotBlank(versionId)) {
+ urlBuilder.append('/');
+ urlBuilder.append(Constants.PARAM_HISTORY);
+ urlBuilder.append('/');
+ urlBuilder.append(versionId);
+ retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, urlBuilder.toString());
+ }
+ }
+ }
+
+ addTagsToPostOrPut(theContext, theResource, retVal);
+ // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody);
+
+ return retVal;
+ }
+ /** End Patch **/
public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map> theMatchParams) {
StringBuilder b = new StringBuilder();
@@ -185,6 +269,7 @@ public class MethodUtil {
return retVal;
}
+
public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) {
HttpPutClientInvocation retVal;
if (StringUtils.isBlank(theResourceBody)) {