diff --git a/examples/pom.xml.orig.orig b/examples/pom.xml.orig.orig
deleted file mode 100644
index b8f9f336f12..00000000000
--- a/examples/pom.xml.orig.orig
+++ /dev/null
@@ -1,61 +0,0 @@
-
- 4.0.0
-
-
- ca.uhn.hapi.fhir
- hapi-fhir
- 0.8-SNAPSHOT
- ../../pom.xml
-
-
- hapi-fhir-base-examples
- jar
-
- HAPI FHIR - Examples (for site)
-
-
-
- ca.uhn.hapi.fhir
- hapi-fhir-base
- 0.8-SNAPSHOT
-<<<<<<< HEAD:hapi-fhir-base/examples/pom.xml
-=======
-<<<<<<< HEAD
-=======
->>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:examples/pom.xml.orig
-
-
- ca.uhn.hapi.fhir
- hapi-fhir-structures-dstu
- 0.8-SNAPSHOT
-<<<<<<< HEAD:hapi-fhir-base/examples/pom.xml
-=======
->>>>>>> versions
->>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:examples/pom.xml.orig
-
-
- javax.servlet
- javax.servlet-api
- 3.1.0
- provided
-
-
- junit
- junit
- ${junit_version}
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-deploy-plugin
-
- true
-
-
-
-
-
-
diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java
index 563a2a2a09a..e891d6da18a 100644
--- a/examples/src/main/java/example/GenericClientExample.java
+++ b/examples/src/main/java/example/GenericClientExample.java
@@ -11,9 +11,12 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
+import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
public class GenericClientExample {
@@ -48,12 +51,19 @@ public class GenericClientExample {
// Invoke the server create method (and send pretty-printed JSON
// encoding to the server
// instead of the default which is non-pretty printed XML)
- client
- .create()
+ 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)
+ IdDt id = outcome.getId();
+ System.out.println("Got ID: " + id.getValue());
// END SNIPPET: create
}
{
@@ -69,15 +79,47 @@ public class GenericClientExample {
// have one though)
patient.setId("Patient/123");
- // Invoke the server create method (and send pretty-printed JSON
- // encoding to the server
- // instead of the default which is non-pretty printed XML)
- client
- .update()
+ // 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)
+ IdDt id = outcome.getId();
+ System.out.println("Got ID: " + id.getValue());
// END SNIPPET: update
}
+ {
+ // START SNIPPET: etagupdate
+ // First, let's retrive 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.getId().getVersionIdPart());
+
+ // Now let's make a change to the resource
+ patient.setGender(AdministrativeGenderCodesEnum.F);
+
+ // 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
@@ -180,23 +222,46 @@ public class GenericClientExample {
{
// START SNIPPET: read
- IdDt id = new IdDt("Patient", "123");
- Patient patient = client.read(Patient.class, id); // search for patient 123
+ // search for patient 123
+ Patient patient = client.read()
+ .resource(Patient.class)
+ .withId("123")
+ .execute();
// END SNIPPET: read
}
{
// START SNIPPET: vread
- IdDt id = new IdDt("Patient", "123", "888");
- Patient patient = client.vread(Patient.class, id); // search for version 888 of patient 123
+ // search for patient 123 (specific version 888)
+ Patient patient = client.read()
+ .resource(Patient.class)
+ .withIdAndVersion("123", "888")
+ .execute();
// END SNIPPET: vread
}
{
// START SNIPPET: readabsolute
- IdDt id = new IdDt("http://example.com/fhir/Patient/123");
- Patient patient = client.read(Patient.class, id); // search for patient 123 on example.com
+ // 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
+ }
diff --git a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java b/examples/src/main/java/example/RestfulPatientResourceProviderMore.java
index f454c958fbf..3b1f81482e3 100644
--- a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java
+++ b/examples/src/main/java/example/RestfulPatientResourceProviderMore.java
@@ -72,6 +72,7 @@ import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@@ -661,11 +662,13 @@ public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient t
String versionId = theId.getVersionIdPart();
if (versionId != null) {
- // If the client passed in a version number in the request URL, which means they are
+ // If the client passed in a version number in an If-Match header, they are
// doing a version-aware update. You may wish to throw an exception if the supplied
- // version is not the latest version.
+ // version is not the latest version. Note that as of DSTU2 the FHIR specification uses
+ // ETags and If-Match to handle version aware updates, so PreconditionFailedException (HTTP 412)
+ // is used instead of ResourceVersionConflictException (HTTP 409)
if (detectedVersionConflict) {
- throw new ResourceVersionConflictException("Invalid version");
+ throw new PreconditionFailedException("Unexpected version");
}
}
diff --git a/examples/src/main/java/example/ServerETagExamples.java b/examples/src/main/java/example/ServerETagExamples.java
new file mode 100644
index 00000000000..b85f42c39ad
--- /dev/null
+++ b/examples/src/main/java/example/ServerETagExamples.java
@@ -0,0 +1,29 @@
+package example;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
+import ca.uhn.fhir.rest.server.RestfulServer;
+
+@SuppressWarnings("serial")
+public class ServerETagExamples {
+
+ // START SNIPPET: disablingETags
+ @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
+ public class RestfulServerWithLogging extends RestfulServer {
+
+ @Override
+ protected void initialize() throws ServletException {
+ // ... define your resource providers here ...
+
+ // ETag support is enabled by default
+ setETagSupport(ETagSupportEnum.ENABLED);
+ }
+
+ }
+ // END SNIPPET: disablingETags
+
+
+
+}
diff --git a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
index 42246e57dab..1c96a5fe526 100644
--- a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
+++ b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs
@@ -25,9 +25,9 @@ org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
@@ -36,12 +36,12 @@ org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
@@ -79,7 +79,7 @@ org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
@@ -94,7 +94,7 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java.orig b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java.orig
deleted file mode 100644
index 199bfc5fa40..00000000000
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java.orig
+++ /dev/null
@@ -1,409 +0,0 @@
-package ca.uhn.fhir.model.primitive;
-
-/*
- * #%L
- * HAPI FHIR - Core Library
- * %%
- * Copyright (C) 2014 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 static ca.uhn.fhir.model.api.TemporalPrecisionEnum.*;
-
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.TimeZone;
-import java.util.regex.Pattern;
-
-import org.apache.commons.lang3.Validate;
-import org.apache.commons.lang3.time.DateUtils;
-import org.apache.commons.lang3.time.FastDateFormat;
-
-import ca.uhn.fhir.model.api.BasePrimitive;
-import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
-import ca.uhn.fhir.parser.DataFormatException;
-
-public abstract class BaseDateTimeDt extends BasePrimitive {
-
- /*
- * Add any new formatters to the static block below!!
- */
- private static final List ourFormatters;
- private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
- private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
- private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
- private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd");
- private static final FastDateFormat ourYearMonthDayNoDashesFormat = FastDateFormat.getInstance("yyyyMMdd");
- private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
- private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
- private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
- private static final FastDateFormat ourYearMonthDayTimeMilliUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC"));
- private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
- private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"));
- private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
- private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
- private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM");
- private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
- private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
-
- static {
- ArrayList formatters = new ArrayList();
- formatters.add(ourYearFormat);
- formatters.add(ourYearMonthDayFormat);
- formatters.add(ourYearMonthDayNoDashesFormat);
- formatters.add(ourYearMonthDayTimeFormat);
- formatters.add(ourYearMonthDayTimeMilliFormat);
- formatters.add(ourYearMonthDayTimeUTCZFormat);
- formatters.add(ourYearMonthDayTimeMilliUTCZFormat);
- formatters.add(ourYearMonthDayTimeMilliZoneFormat);
- formatters.add(ourYearMonthDayTimeZoneFormat);
- formatters.add(ourYearMonthFormat);
- formatters.add(ourYearMonthNoDashesFormat);
- ourFormatters = Collections.unmodifiableList(formatters);
- }
- private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
-
- private TimeZone myTimeZone;
- private boolean myTimeZoneZulu = false;
-
- private void clearTimeZone() {
- myTimeZone = null;
- myTimeZoneZulu = false;
-<<<<<<< HEAD
- }
-
- /**
- * Gets the precision for this datatype using field values from {@link Calendar}, such as {@link Calendar#MONTH}. Default is {@link Calendar#DAY_OF_MONTH}
- *
- * @see #setPrecision(int)
- */
- public TemporalPrecisionEnum getPrecision() {
- return myPrecision;
- }
-
- public TimeZone getTimeZone() {
- return myTimeZone;
- }
-
- @Override
- public Date getValue() {
- return myValue;
-=======
->>>>>>> issue50
- }
-
- @Override
- protected String encode(Date theValue) {
- if (theValue == null) {
- return null;
- } else {
- switch (myPrecision) {
- case DAY:
- return ourYearMonthDayFormat.format(theValue);
- case MONTH:
- return ourYearMonthFormat.format(theValue);
- case YEAR:
- return ourYearFormat.format(theValue);
- case SECOND:
- if (myTimeZoneZulu) {
- GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
- cal.setTime(theValue);
- return ourYearMonthDayTimeFormat.format(cal) + "Z";
- } else if (myTimeZone != null) {
- GregorianCalendar cal = new GregorianCalendar(myTimeZone);
- cal.setTime(theValue);
- return ourYearMonthDayTimeZoneFormat.format(cal);
- } else {
- return ourYearMonthDayTimeFormat.format(theValue);
- }
- case MILLI:
- if (myTimeZoneZulu) {
- GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
- cal.setTime(theValue);
- return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
- } else if (myTimeZone != null) {
- GregorianCalendar cal = new GregorianCalendar(myTimeZone);
- cal.setTime(theValue);
- return ourYearMonthDayTimeMilliZoneFormat.format(cal);
- } else {
- return ourYearMonthDayTimeMilliFormat.format(theValue);
- }
- }
- throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
- }
- }
-
- /**
-<<<<<<< HEAD
-=======
- * Gets the precision for this datatype using field values from {@link Calendar}, such as {@link Calendar#MONTH}. Default is {@link Calendar#DAY_OF_MONTH}
- *
- * @see #setPrecision(int)
- */
- public TemporalPrecisionEnum getPrecision() {
- return myPrecision;
- }
-
- public TimeZone getTimeZone() {
- return myTimeZone;
- }
-
- /**
->>>>>>> issue50
- * To be implemented by subclasses to indicate whether the given precision is allowed by this type
- */
- abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
-
- public boolean isTimeZoneZulu() {
- return myTimeZoneZulu;
- }
-
- /**
- * Returns true if this object represents a date that is today's date
- *
- * @throws NullPointerException
- * if {@link #getValue()} returns null
- */
- public boolean isToday() {
-<<<<<<< HEAD
- Validate.notNull(myValue, getClass().getSimpleName() + " contains null value");
- return DateUtils.isSameDay(new Date(), myValue);
- }
-
- /**
- * Sets the precision for this datatype using field values from {@link Calendar}. Valid values are:
- *
- *
{@link Calendar#SECOND}
- *
{@link Calendar#DAY_OF_MONTH}
- *
{@link Calendar#MONTH}
- *
{@link Calendar#YEAR}
- *
- *
- * @throws DataFormatException
- */
- public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
- if (thePrecision == null) {
- throw new NullPointerException("Precision may not be null");
- }
- myPrecision = thePrecision;
- }
-
- private void setTimeZone(String theValueString, boolean hasMillis) {
- clearTimeZone();
- int timeZoneStart = 19;
- if (hasMillis)
- timeZoneStart += 4;
- if (theValueString.endsWith("Z")) {
- setTimeZoneZulu(true);
- } else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
- setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
- } else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
- setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
- }
- }
-
- public void setTimeZone(TimeZone theTimeZone) {
- myTimeZone = theTimeZone;
- }
-
- public void setTimeZoneZulu(boolean theTimeZoneZulu) {
- myTimeZoneZulu = theTimeZoneZulu;
-=======
- Validate.notNull(getValue(), getClass().getSimpleName() + " contains null value");
- return DateUtils.isSameDay(new Date(), getValue());
->>>>>>> issue50
- }
-
- @Override
- protected Date parse(String theValue) throws DataFormatException {
- try {
- if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
- if (isPrecisionAllowed(YEAR)) {
- setPrecision(YEAR);
- clearTimeZone();
- return ((ourYearFormat).parse(theValue));
- } else {
- throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
- }
- } else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
- // Eg. 198401 (allow this just to be lenient)
- if (isPrecisionAllowed(MONTH)) {
- setPrecision(MONTH);
- clearTimeZone();
- return ((ourYearMonthNoDashesFormat).parse(theValue));
- } else {
- throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
- }
- } else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
- // E.g. 1984-01 (this is valid according to the spec)
- if (isPrecisionAllowed(MONTH)) {
- setPrecision(MONTH);
- clearTimeZone();
- return ((ourYearMonthFormat).parse(theValue));
- } else {
- throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
- }
- } else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
- // Eg. 19840101 (allow this just to be lenient)
- if (isPrecisionAllowed(DAY)) {
- setPrecision(DAY);
- clearTimeZone();
- return ((ourYearMonthDayNoDashesFormat).parse(theValue));
- } else {
- throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
- }
- } else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
- // E.g. 1984-01-01 (this is valid according to the spec)
- if (isPrecisionAllowed(DAY)) {
- setPrecision(DAY);
- clearTimeZone();
- return ((ourYearMonthDayFormat).parse(theValue));
- } else {
- throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
- }
- } else if (theValue.length() >= 18) { // date and time with possible time zone
- int dotIndex = theValue.indexOf('.', 18);
- boolean hasMillis = dotIndex > -1;
-
- if (!hasMillis && !isPrecisionAllowed(SECOND)) {
- throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
- } else if (hasMillis && !isPrecisionAllowed(MILLI)) {
- throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
- }
-
-<<<<<<< HEAD
- if (hasMillis) {
- try {
- if (hasOffset(theValue)) {
- myValue = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
- } else if (theValue.endsWith("Z"))
- myValue = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
- else
- myValue = ourYearMonthDayTimeMilliFormat.parse(theValue);
- } catch (ParseException p2) {
- throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
- }
- setTimeZone(theValue, hasMillis);
-=======
- Calendar cal;
- try {
- cal = DatatypeConverter.parseDateTime(theValue);
- } catch (IllegalArgumentException e) {
- throw new DataFormatException("Invalid data/time string (" + e.getMessage() + "): " + theValue);
- }
- if (dotIndex == -1) {
- setPrecision(TemporalPrecisionEnum.SECOND);
- } else {
->>>>>>> issue50
- setPrecision(TemporalPrecisionEnum.MILLI);
- } else {
- try {
- if (hasOffset(theValue)) {
- myValue = ourYearMonthDayTimeZoneFormat.parse(theValue);
- } else if (theValue.endsWith("Z")) {
- myValue = ourYearMonthDayTimeUTCZFormat.parse(theValue);
- } else {
- myValue = ourYearMonthDayTimeFormat.parse(theValue);
- }
- } catch (ParseException p2) {
- throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
- }
-
- setTimeZone(theValue, hasMillis);
- setPrecision(TemporalPrecisionEnum.SECOND);
- }
-<<<<<<< HEAD
-=======
-
- return cal.getTime();
->>>>>>> issue50
- } else {
- throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
- }
- } catch (ParseException e) {
- throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
- }
- }
-
-<<<<<<< HEAD
- private boolean hasOffset(String theValue) {
- boolean inTime = false;
- for (int i = 0; i < theValue.length(); i++) {
- switch (theValue.charAt(i)) {
- case 'T':
- inTime = true;
- break;
- case '+':
- case '-':
- if (inTime) {
- return true;
- }
- break;
- }
- }
- return false;
- }
-
- /**
- * For unit tests only
- */
- static List getFormatters() {
- return ourFormatters;
-=======
- /**
- * Sets the precision for this datatype using field values from {@link Calendar}. Valid values are:
- *
- *
{@link Calendar#SECOND}
- *
{@link Calendar#DAY_OF_MONTH}
- *
{@link Calendar#MONTH}
- *
{@link Calendar#YEAR}
- *
- *
- * @throws DataFormatException
- */
- public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
- if (thePrecision == null) {
- throw new NullPointerException("Precision may not be null");
- }
- myPrecision = thePrecision;
- }
-
- public void setTimeZone(TimeZone theTimeZone) {
- myTimeZone = theTimeZone;
- }
-
- public void setTimeZoneZulu(boolean theTimeZoneZulu) {
- myTimeZoneZulu = theTimeZoneZulu;
- }
-
- @Override
- public void setValue(Date theValue) throws DataFormatException {
- clearTimeZone();
- super.setValue(theValue);
- }
-
- @Override
- public void setValueAsString(String theValue) throws DataFormatException {
- clearTimeZone();
- super.setValueAsString(theValue);
->>>>>>> issue50
- }
-
-}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java
index c0e25cd1c3a..5b4e6d4332e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java
@@ -451,6 +451,11 @@ public class IdDt implements IPrimitiveDatatype {
return getValue();
}
+ /**
+ * Returns a new IdDt containing this IdDt's values but with no server base URL if one
+ * is present in this IdDt. For example, if this IdDt contains the ID "http://foo/Patient/1",
+ * this method will return a new IdDt containing ID "Patient/1".
+ */
public IdDt toUnqualified() {
return new IdDt(getResourceType(), getIdPart(), getVersionIdPart());
}
@@ -550,5 +555,21 @@ public class IdDt implements IPrimitiveDatatype {
throw new IllegalArgumentException("Unknown resource class type, does not implement IResource or extend Resource");
}
}
-
+
+ /**
+ * Retrieves the ID from the given resource instance
+ */
+ public static IdDt of(IBaseResource theResouce) {
+ if (theResouce == null) {
+ throw new NullPointerException("theResource can not be null");
+ } else if (theResouce instanceof IResource) {
+ return ((IResource) theResouce).getId();
+ } else if (theResouce instanceof Resource) {
+ // TODO: implement
+ throw new UnsupportedOperationException();
+ } else {
+ throw new IllegalArgumentException("Unknown resource class type, does not implement IResource or extend Resource");
+ }
+ }
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig
deleted file mode 100644
index 9084e3999fd..00000000000
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java.orig
+++ /dev/null
@@ -1,556 +0,0 @@
-package ca.uhn.fhir.model.primitive;
-
-/*
- * #%L
- * HAPI FHIR - Core Library
- * %%
- * Copyright (C) 2014 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 static org.apache.commons.lang3.StringUtils.*;
-
-import java.math.BigDecimal;
-
-import org.apache.commons.lang3.ObjectUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Validate;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-<<<<<<< HEAD
-import org.hl7.fhir.instance.model.IBaseResource;
-import org.hl7.fhir.instance.model.Resource;
-=======
-import org.hamcrest.core.IsNot;
->>>>>>> c294e1c064fcbf112edcbf4e10c341691c12a1a8
-
-import ca.uhn.fhir.model.api.IPrimitiveDatatype;
-import ca.uhn.fhir.model.api.IResource;
-import ca.uhn.fhir.model.api.annotation.DatatypeDef;
-import ca.uhn.fhir.model.api.annotation.SimpleSetter;
-import ca.uhn.fhir.parser.DataFormatException;
-import ca.uhn.fhir.rest.server.Constants;
-import ca.uhn.fhir.util.UrlUtil;
-
-/**
- * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource.
- *
- *
- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
- * limit of 36 characters.
- *
- *
- * regex: [a-z0-9\-\.]{1,36}
- *
- */
-@DatatypeDef(name = "id")
-public class IdDt implements IPrimitiveDatatype {
-
- private String myBaseUrl;
- private boolean myHaveComponentParts;
- private String myResourceType;
- private String myUnqualifiedId;
- private String myUnqualifiedVersionId;
- private volatile String myValue;
-
- /**
- * Create a new empty ID
- */
- public IdDt() {
- super();
- }
-
- /**
- * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation.
- */
- public IdDt(BigDecimal thePid) {
- if (thePid != null) {
- setValue(toPlainStringWithNpeThrowIfNeeded(thePid));
- } else {
- setValue(null);
- }
- }
-
- /**
- * Create a new ID using a long
- */
- public IdDt(long theId) {
- setValue(Long.toString(theId));
- }
-
- /**
- * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete URL (http://example.com/fhir/Patient/1234).
- *
- *
- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
- * limit of 36 characters.
- *
- *
- * regex: [a-z0-9\-\.]{1,36}
- *
- */
- @SimpleSetter
- public IdDt(@SimpleSetter.Parameter(name = "theId") String theValue) {
- setValue(theValue);
- }
-
- /**
- * Constructor
- *
- * @param theResourceType
- * The resource type (e.g. "Patient")
- * @param theId
- * The ID (e.g. "123")
- */
- public IdDt(String theResourceType, BigDecimal theIdPart) {
- this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart));
- }
-
- /**
- * Constructor
- *
- * @param theResourceType
- * The resource type (e.g. "Patient")
- * @param theId
- * The ID (e.g. "123")
- */
- public IdDt(String theResourceType, String theId) {
- this(theResourceType, theId, null);
- }
-
- /**
- * Constructor
- *
- * @param theResourceType
- * The resource type (e.g. "Patient")
- * @param theId
- * The ID (e.g. "123")
- * @param theVersionId
- * The version ID ("e.g. "456")
- */
- public IdDt(String theResourceType, String theId, String theVersionId) {
- this(null,theResourceType,theId,theVersionId);
- }
-
- /**
- * Constructor
- *
- * @param theBaseUrl
- * The server base URL (e.g. "http://example.com/fhir")
- * @param theResourceType
- * The resource type (e.g. "Patient")
- * @param theId
- * The ID (e.g. "123")
- * @param theVersionId
- * The version ID ("e.g. "456")
- */
- public IdDt(String theBaseUrl, String theResourceType, String theId, String theVersionId) {
- myBaseUrl = theBaseUrl;
- myResourceType = theResourceType;
- myUnqualifiedId = theId;
- myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null);
- myHaveComponentParts = true;
- }
-
- /**
- * Creates an ID based on a given URL
- */
- public IdDt(UriDt theUrl) {
- setValue(theUrl.getValueAsString());
- }
-
- /**
- * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous)
- */
- public BigDecimal asBigDecimal() {
- return getIdPartAsBigDecimal();
- }
-
- /**
- * Returns true if this IdDt matches the given IdDt in terms of resource type and ID, but ignores the URL base
- */
- @SuppressWarnings("deprecation")
- public boolean equalsIgnoreBase(IdDt theId) {
- if (theId == null) {
- return false;
- }
- if (theId.isEmpty()) {
- return isEmpty();
- }
- return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
- }
-
-
-
- @Override
- public boolean equals(Object theArg0) {
- if (!(theArg0 instanceof IdDt)) {
- return false;
- }
- IdDt id = (IdDt)theArg0;
- return StringUtils.equals(getValueAsString(), id.getValueAsString());
- }
-
- @Override
- public int hashCode() {
- HashCodeBuilder b = new HashCodeBuilder();
- b.append(getValueAsString());
- return b.toHashCode();
- }
-
- /**
- * Returns the portion of this resource ID which corresponds to the server base URL. For example given the resource ID http://example.com/fhir/Patient/123 the base URL would be
- * http://example.com/fhir.
- *
- * This method may return null if the ID contains no base (e.g. "Patient/123")
- *
- */
- public String getBaseUrl() {
- return myBaseUrl;
- }
-
- public String getIdPart() {
- return myUnqualifiedId;
- }
-
- /**
- * Returns the unqualified portion of this ID as a big decimal, or null if the value is null
- *
- * @throws NumberFormatException
- * If the value is not a valid BigDecimal
- */
- public BigDecimal getIdPartAsBigDecimal() {
- String val = getIdPart();
- if (isBlank(val)) {
- return null;
- }
- return new BigDecimal(val);
- }
-
- /**
- * Returns the unqualified portion of this ID as a {@link Long}, or null if the value is null
- *
- * @throws NumberFormatException
- * If the value is not a valid Long
- */
- public Long getIdPartAsLong() {
- String val = getIdPart();
- if (isBlank(val)) {
- return null;
- }
- return Long.parseLong(val);
- }
-
- public String getResourceType() {
- return myResourceType;
- }
-
- /**
- * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get just the ID portion.
- *
- * @see #getIdPart()
- */
- @Override
- public String getValue() {
- if (myValue == null && myHaveComponentParts) {
- StringBuilder b = new StringBuilder();
- if (isNotBlank(myBaseUrl)) {
- b.append(myBaseUrl);
- if (myBaseUrl.charAt(myBaseUrl.length()-1)!='/') {
- b.append('/');
- }
- }
-
- if (isNotBlank(myResourceType)) {
- b.append(myResourceType);
- }
-
- if (b.length() > 0) {
- b.append('/');
- }
-
- b.append(myUnqualifiedId);
- if (isNotBlank(myUnqualifiedVersionId)) {
- b.append('/');
- b.append(Constants.PARAM_HISTORY);
- b.append('/');
- b.append(myUnqualifiedVersionId);
- }
- myValue = b.toString();
- }
- return myValue;
- }
-
- @Override
- public String getValueAsString() {
- return getValue();
- }
-
- public String getVersionIdPart() {
- return myUnqualifiedVersionId;
- }
-
- public Long getVersionIdPartAsLong() {
- if (!hasVersionIdPart()) {
- return null;
- } else {
- return Long.parseLong(getVersionIdPart());
- }
- }
-
- /**
- * Returns true if this ID has a base url
- *
- * @see #getBaseUrl()
- */
- public boolean hasBaseUrl() {
- return isNotBlank(myBaseUrl);
- }
-
- public boolean hasIdPart() {
- return isNotBlank(getIdPart());
- }
-
- public boolean hasResourceType() {
- return isNotBlank(myResourceType);
- }
-
- public boolean hasVersionIdPart() {
- return isNotBlank(getVersionIdPart());
- }
-
- /**
- * Returns true if this ID contains an absolute URL (in other words, a URL starting with "http://" or "https://"
- */
- public boolean isAbsolute() {
- if (StringUtils.isBlank(getValue())) {
- return false;
- }
- return UrlUtil.isAbsolute(getValue());
- }
-
- /**
- * Returns true if the unqualified ID is a valid {@link Long} value (in other words, it consists only of digits)
- */
- public boolean isIdPartValidLong() {
- String id = getIdPart();
- if (StringUtils.isBlank(id)) {
- return false;
- }
- for (int i = 0; i < id.length(); i++) {
- if (Character.isDigit(id.charAt(i)) == false) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns true if the ID is a local reference (in other words, it begins with the '#' character)
- */
- public boolean isLocal() {
- return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
- }
-
- /**
- * Copies the value from the given IdDt to this IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API.
- */
- public void setId(IdDt theId) {
- setValue(theId.getValue());
- }
-
- /**
- * Set the value
- *
- *
- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
- * limit of 36 characters.
- *
- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
- * limit of 36 characters.
- *
- *
- * regex: [a-z0-9\-\.]{1,36}
- *
- */
- @Override
- public void setValueAsString(String theValue) throws DataFormatException {
- setValue(theValue);
- }
-
- @Override
- public String toString() {
- return getValue();
- }
-
- public IdDt toUnqualified() {
- return new IdDt(getResourceType(), getIdPart(), getVersionIdPart());
- }
-
- public IdDt toUnqualifiedVersionless() {
- return new IdDt(getResourceType(), getIdPart());
- }
-
- public IdDt toVersionless() {
- String value = getValue();
- int i = value.indexOf(Constants.PARAM_HISTORY);
- if (i > 1) {
- return new IdDt(value.substring(0, i - 1));
- } else {
- return this;
- }
- }
-
- public IdDt withResourceType(String theResourceName) {
- return new IdDt(theResourceName, getIdPart(), getVersionIdPart());
- }
-
- /**
- * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be used if the ID does not already contain those respective parts). Essentially,
- * because IdDt can contain either a complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete URL.
- *
- * @param theServerBase
- * The server base (e.g. "http://example.com/fhir")
- * @param theResourceType
- * The resource name (e.g. "Patient")
- * @return A fully qualified URL for this ID (e.g. "http://example.com/fhir/Patient/1")
- */
- public String withServerBase(String theServerBase, String theResourceType) {
- if (getValue().startsWith("http")) {
- return getValue();
- }
- StringBuilder retVal = new StringBuilder();
- retVal.append(theServerBase);
- if (retVal.charAt(retVal.length() - 1) != '/') {
- retVal.append('/');
- }
- if (isNotBlank(getResourceType())) {
- retVal.append(getResourceType());
- } else {
- retVal.append(theResourceType);
- }
- retVal.append('/');
- retVal.append(getIdPart());
-
- if (hasVersionIdPart()) {
- retVal.append('/');
- retVal.append(Constants.PARAM_HISTORY);
- retVal.append('/');
- retVal.append(getVersionIdPart());
- }
-
- return retVal.toString();
- }
-
- /**
- * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion.
- *
- * @param theVersion
- * The actual version string, e.g. "1"
- * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion.
- */
- public IdDt withVersion(String theVersion) {
- Validate.notBlank(theVersion, "Version may not be null or empty");
-
- String existingValue = getValue();
-
- int i = existingValue.indexOf(Constants.PARAM_HISTORY);
- String value;
- if (i > 1) {
- value = existingValue.substring(0, i - 1);
- } else {
- value = existingValue;
- }
-
- return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion);
- }
-
- private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
- if (theIdPart == null) {
- throw new NullPointerException("BigDecimal ID can not be null");
- }
- return theIdPart.toPlainString();
- }
-
- @Override
- public boolean isEmpty() {
- return isBlank(getValue());
- }
-
- public void applyTo(IBaseResource theResouce) {
- if (theResouce == null) {
- throw new NullPointerException("theResource can not be null");
- } else if (theResouce instanceof IResource) {
- ((IResource) theResouce).setId(new IdDt(getValue()));
- } else if (theResouce instanceof Resource) {
- ((Resource) theResouce).setId(getIdPart());
- } else {
- throw new IllegalArgumentException("Unknown resource class type, does not implement IResource or extend Resource");
- }
- }
-
-}
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 91992796735..f04ec211933 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
@@ -65,11 +65,16 @@ import ca.uhn.fhir.rest.gclient.IGetPageTyped;
import ca.uhn.fhir.rest.gclient.IGetTags;
import ca.uhn.fhir.rest.gclient.IParam;
import ca.uhn.fhir.rest.gclient.IQuery;
+import ca.uhn.fhir.rest.gclient.IRead;
+import ca.uhn.fhir.rest.gclient.IReadExecutable;
+import ca.uhn.fhir.rest.gclient.IReadIfNoneMatch;
+import ca.uhn.fhir.rest.gclient.IReadTyped;
import ca.uhn.fhir.rest.gclient.ISort;
import ca.uhn.fhir.rest.gclient.ITransaction;
import ca.uhn.fhir.rest.gclient.ITransactionTyped;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.IUpdate;
+import ca.uhn.fhir.rest.gclient.IUpdateExecutable;
import ca.uhn.fhir.rest.gclient.IUpdateTyped;
import ca.uhn.fhir.rest.method.DeleteMethodBinding;
import ca.uhn.fhir.rest.method.HistoryMethodBinding;
@@ -87,6 +92,8 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
+import ca.uhn.fhir.util.ICallable;
/**
* @author James Agnew
@@ -121,7 +128,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
Class conformance = (Class) myContext.getResourceDefinition("Conformance").getImplementingClass();
-
+
ResourceResponseHandler extends BaseConformance> binding = new ResourceResponseHandler(conformance, null);
BaseConformance resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
return resp;
@@ -232,23 +239,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public T read(final Class theType, IdDt theId) {
- if (theId == null || theId.hasIdPart() == false) {
- throw new IllegalArgumentException("theId does not contain a valid ID, is: " + theId);
- }
-
- HttpGetClientInvocation invocation;
- if (theId.hasBaseUrl()) {
- invocation = ReadMethodBinding.createAbsoluteReadInvocation(theId.toVersionless());
- } else {
- invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
- }
- if (isKeepResponses()) {
- myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
- }
-
- ResourceResponseHandler binding = new ResourceResponseHandler(theType, theId);
- T resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
- return resp;
+ return doReadOrVRead(theType, theId, false, null, null);
}
@Override
@@ -420,30 +411,54 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public T vread(final Class theType, IdDt theId) {
+ public T vread(final Class theType, IdDt theId) {
+ if (theId.hasVersionIdPart() == false) {
+ throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
+ }
+ return doReadOrVRead(theType, theId, true, null, null);
+ }
+
+ private T doReadOrVRead(final Class theType, IdDt theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches) {
String resName = toResourceName(theType);
IdDt id = theId;
if (!id.hasBaseUrl()) {
id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart());
}
- if (id.hasVersionIdPart() == false) {
- throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
- }
-
HttpGetClientInvocation invocation;
if (id.hasBaseUrl()) {
- invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id);
+ if (theVRead) {
+ invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id);
+ } else {
+ invocation = ReadMethodBinding.createAbsoluteReadInvocation(id);
+ }
} else {
- invocation = ReadMethodBinding.createVReadInvocation(id);
+ if (theVRead) {
+ invocation = ReadMethodBinding.createVReadInvocation(id, resName);
+ } else {
+ invocation = ReadMethodBinding.createReadInvocation(id, resName);
+ }
}
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
}
+ if (theIfVersionMatches != null) {
+ invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"');
+ }
+
ResourceResponseHandler binding = new ResourceResponseHandler(theType, id);
- T resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
- return resp;
+
+ if (theNotModifiedHandler == null) {
+ return invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
+ } else {
+ try {
+ return invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
+ } catch (NotModifiedException e) {
+ return theNotModifiedHandler.call();
+ }
+ }
+
}
/* also deprecated in interface */
@@ -454,7 +469,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public T vread(Class theType, String theId, String theVersionId) {
+ public T vread(Class theType, String theId, String theVersionId) {
IdDt resId = new IdDt(toResourceName(theType), theId, theVersionId);
return vread(theType, resId);
}
@@ -549,8 +564,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
- BaseServerResponseException {
+ public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@@ -577,7 +591,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (getParamEncoding() != null) {
myResourceBody = null;
}
-
+
BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext);
RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
@@ -688,6 +702,132 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
+ @Override
+ public IRead read() {
+ return new ReadInternal();
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable {
+ private RuntimeResourceDefinition myType;
+ private IdDt myId;
+ private ICallable myNotModifiedHandler;
+ private String myIfVersionMatches;
+
+ @Override
+ public IReadTyped resource(Class theResourceType) {
+ Validate.notNull(theResourceType, "theResourceType must not be null");
+ myType = myContext.getResourceDefinition(theResourceType);
+ if (myType == null) {
+ throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType));
+ }
+ return this;
+ }
+
+ @Override
+ public IReadTyped resource(String theResourceAsText) {
+ Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText");
+ myType = myContext.getResourceDefinition(theResourceAsText);
+ if (myType == null) {
+ throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText));
+ }
+ return this;
+ }
+
+ @Override
+ public IReadExecutable withId(String theId) {
+ Validate.notBlank(theId, "The ID can not be blank");
+ myId = new IdDt(myType.getName(), theId);
+ return this;
+ }
+
+ @Override
+ public IReadExecutable withIdAndVersion(String theId, String theVersion) {
+ Validate.notBlank(theId, "The ID can not be blank");
+ myId = new IdDt(myType.getName(), theId, theVersion);
+ return this;
+ }
+
+ @Override
+ public IReadExecutable withId(IdDt theId) {
+ Validate.notNull(theId, "The ID can not be null");
+ Validate.notBlank(theId.getIdPart(), "The ID can not be blank");
+ myId = theId.toUnqualified();
+ return this;
+ }
+
+ @Override
+ public IReadExecutable withUrl(String theUrl) {
+ myId = new IdDt(theUrl);
+ processUrl();
+ return this;
+ }
+
+ private void processUrl() {
+ String resourceType = myId.getResourceType();
+ if (isBlank(resourceType)) {
+ throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId));
+ }
+ myType = myContext.getResourceDefinition(resourceType);
+ if (myType == null) {
+ throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId));
+ }
+ }
+
+ @Override
+ public Object execute() {
+ if (myId.hasVersionIdPart()) {
+ return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches);
+ } else {
+ return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches);
+ }
+ }
+
+ @Override
+ public IReadExecutable withUrl(IdDt theUrl) {
+ Validate.notNull(theUrl, "theUrl can not be null");
+ myId = theUrl;
+ processUrl();
+ return this;
+ }
+
+ @Override
+ public IReadIfNoneMatch ifVersionMatches(String theVersion) {
+ myIfVersionMatches = theVersion;
+ return new IReadIfNoneMatch() {
+
+ @Override
+ public IReadExecutable returnResource(final IBaseResource theInstance) {
+ myNotModifiedHandler = new ICallable() {
+ @Override
+ public Object call() {
+ return theInstance;
+ }
+ };
+ return ReadInternal.this;
+ }
+
+ @Override
+ public IReadExecutable returnNull() {
+ myNotModifiedHandler = new ICallable() {
+ @Override
+ public Object call() {
+ return null;
+ }
+ };
+ return ReadInternal.this;
+ }
+
+ @Override
+ public IReadExecutable throwNotModifiedException() {
+ myNotModifiedHandler = null;
+ return ReadInternal.this;
+ }
+ };
+ }
+
+ }
+
private class GetTagsInternal extends BaseClientExecutable implements IGetTags {
private String myId;
@@ -776,8 +916,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class OperationOutcomeResponseHandler implements IClientResponseHandler {
@Override
- public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
- BaseServerResponseException {
+ public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
return null;
@@ -791,7 +930,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
ourLog.warn("Failed to parse OperationOutcome response", e);
return null;
}
- MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
+ MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, retVal);
return retVal;
}
@@ -805,8 +944,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
- BaseServerResponseException {
+ public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
response.setCreated(true);
@@ -824,8 +962,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
- BaseServerResponseException {
+ public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
}
}
@@ -849,11 +986,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IParser parser = respType.newParser(myContext);
T retVal = parser.parseResource(myType, theResponseReader);
- if (myId != null) {
- myId.applyTo(retVal);
- }
-
- MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
+ MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
return retVal;
}
@@ -1035,8 +1168,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class TagListResponseHandler implements IClientResponseHandler {
@Override
- public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
- BaseServerResponseException {
+ public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@@ -1091,7 +1223,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
- private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped {
+ private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable {
private IdDt myId;
private IResource myResource;
@@ -1112,7 +1244,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
// If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
if (getParamEncoding() != null) {
myResourceBody = null;
- }
+ }
BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
@@ -1141,7 +1273,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public IUpdateTyped withId(IdDt theId) {
+ public IUpdateExecutable withId(IdDt theId) {
if (theId == null) {
throw new NullPointerException("theId can not be null");
}
@@ -1153,7 +1285,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public IUpdateTyped withId(String theId) {
+ public IUpdateExecutable withId(String theId) {
if (theId == null) {
throw new NullPointerException("theId can not be null");
}
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 dd728a00352..1f310b727ca 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
@@ -38,12 +38,13 @@ import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.IDelete;
import ca.uhn.fhir.rest.gclient.IGetPage;
import ca.uhn.fhir.rest.gclient.IGetTags;
+import ca.uhn.fhir.rest.gclient.IRead;
import ca.uhn.fhir.rest.gclient.ITransaction;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.IUpdate;
public interface IGenericClient {
-
+
/**
* Retrieves and returns the server conformance statement
*/
@@ -103,15 +104,17 @@ public interface IGenericClient {
* Implementation of the "history instance" method.
*
* @param theType
- * The type of resource to return the history for, or null to search for history across all resources
+ * The type of resource to return the history for, or
+ * null to search for history across all resources
* @param theId
- * The ID of the resource to return the history for, or null to search for all resource instances. Note that if this param is not null, theType must also not
- * be null
+ * The ID of the resource to return the history for, or null to search for all resource
+ * instances. Note that if this param is not null, theType must also not be null
* @param theSince
* If not null, request that the server only return resources updated since this time
* @param theLimit
- * If not null, request that the server return no more than this number of resources. Note that the server may return less even if more are available, but should not return more
- * according to the FHIR specification.
+ * If not null, request that the server return no more than this number of resources. Note that the
+ * server may return less even if more are available, but should not return more according to the FHIR
+ * specification.
* @return A bundle containing returned resources
*/
Bundle history(Class theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit);
@@ -120,38 +123,48 @@ public interface IGenericClient {
* Implementation of the "history instance" method.
*
* @param theType
- * The type of resource to return the history for, or null to search for history across all resources
+ * The type of resource to return the history for, or
+ * null to search for history across all resources
* @param theId
- * The ID of the resource to return the history for, or null to search for all resource instances. Note that if this param is not null, theType must also not
- * be null
+ * The ID of the resource to return the history for, or null to search for all resource
+ * instances. Note that if this param is not null, theType must also not be null
* @param theSince
* If not null, request that the server only return resources updated since this time
* @param theLimit
- * If not null, request that the server return no more than this number of resources. Note that the server may return less even if more are available, but should not return more
- * according to the FHIR specification.
+ * If not null, request that the server return no more than this number of resources. Note that the
+ * server may return less even if more are available, but should not return more according to the FHIR
+ * specification.
* @return A bundle containing returned resources
*/
Bundle history(Class theType, String theIdDt, DateTimeDt theSince, Integer theLimit);
/**
- * Loads the previous/next bundle of resources from a paged set, using the link specified in the "link type=next" tag within the atom bundle.
+ * Loads the previous/next bundle of resources from a paged set, using the link specified in the "link type=next"
+ * tag within the atom bundle.
*
* @see Bundle#getLinkNext()
*/
IGetPage loadPage();
/**
- * Implementation of the "instance read" method. This method will only ever do a "read" for the latest version of a given resource instance, even if the ID passed in contains a version. If you
- * wish to request a specific version of a resource (the "vread" operation), use {@link #vread(Class, IdDt)} instead.
+ * Fluent method for "read" and "vread" methods.
+ */
+ IRead read();
+
+ /**
+ * Implementation of the "instance read" method. This method will only ever do a "read" for the latest version of a
+ * given resource instance, even if the ID passed in contains a version. If you wish to request a specific version
+ * of a resource (the "vread" operation), use {@link #vread(Class, IdDt)} instead.
*
- * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the resource type and ID) the server base for the client will be ignored, and the URL
- * passed in will be queried.
+ * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the
+ * resource type and ID) the server base for the client will be ignored, and the URL passed in will be queried.
*
*
* @param theType
* The type of resource to load
* @param theId
- * The ID to load, including the resource ID and the resource version ID. Valid values include "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
+ * The ID to load, including the resource ID and the resource version ID. Valid values include
+ * "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
* @return The resource
*/
T read(Class theType, IdDt theId);
@@ -188,7 +201,8 @@ public interface IGenericClient {
IResource read(UriDt theUrl);
/**
- * Register a new interceptor for this client. An interceptor can be used to add additional logging, or add security headers, or pre-process responses, etc.
+ * Register a new interceptor for this client. An interceptor can be used to add additional logging, or add security
+ * headers, or pre-process responses, etc.
*/
void registerInterceptor(IClientInterceptor theInterceptor);
@@ -218,8 +232,8 @@ public interface IGenericClient {
Bundle search(UriDt theUrl);
/**
- * If set to true, the client will log all requests and all responses. This is probably not a good production setting since it will result in a lot of extra logging, but it can be
- * useful for troubleshooting.
+ * If set to true, the client will log all requests and all responses. This is probably not a good
+ * production setting since it will result in a lot of extra logging, but it can be useful for troubleshooting.
*
* @param theLogRequestAndResponse
* Should requests and responses be logged
@@ -236,14 +250,16 @@ public interface IGenericClient {
*
* @param theResources
* The resources to create/update in a single transaction
- * @return A list of resource stubs (these will not be fully populated) containing IDs and other {@link IResource#getResourceMetadata() metadata}
+ * @return A list of resource stubs (these will not be fully populated) containing IDs and other
+ * {@link IResource#getResourceMetadata() metadata}
* @deprecated Use {@link #transaction()}
*
*/
List transaction(List theResources);
/**
- * Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
+ * Remove an intercaptor that was previously registered using
+ * {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);
@@ -286,19 +302,21 @@ public interface IGenericClient {
MethodOutcome validate(IResource theResource);
/**
- * Implementation of the "instance vread" method. Note that this method expects theId to contain a resource ID as well as a version ID, and will fail if it does not.
+ * Implementation of the "instance vread" method. Note that this method expects theId to contain a
+ * resource ID as well as a version ID, and will fail if it does not.
*
- * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the resource type and ID) the server base for the client will be ignored, and the URL
- * passed in will be queried.
+ * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the
+ * resource type and ID) the server base for the client will be ignored, and the URL passed in will be queried.
*
*
* @param theType
* The type of resource to load
* @param theId
- * The ID to load, including the resource ID and the resource version ID. Valid values include "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
+ * The ID to load, including the resource ID and the resource version ID. Valid values include
+ * "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
* @return The resource
*/
- T vread(Class theType, IdDt theId);
+ T vread(Class theType, IdDt theId);
/**
* Implementation of the "instance vread" method.
@@ -325,6 +343,6 @@ public interface IGenericClient {
* The version ID
* @return The resource
*/
- T vread(Class theType, String theId, String theVersionId);
+ T vread(Class theType, String theId, String theVersionId);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IRead.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IRead.java
new file mode 100644
index 00000000000..3c83e43dff4
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IRead.java
@@ -0,0 +1,35 @@
+package ca.uhn.fhir.rest.gclient;
+
+import org.hl7.fhir.instance.model.IBaseResource;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 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 IRead {
+ /**
+ * Construct a read for the given resource type (e.g. Patient.class)
+ */
+ IReadTyped resource(Class theResourceType);
+
+ /**
+ * Construct a read for the given resource type (e.g. "Patient")
+ */
+ IReadTyped resource(String theResourceType);
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java
new file mode 100644
index 00000000000..29c2efc837a
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadExecutable.java
@@ -0,0 +1,20 @@
+package ca.uhn.fhir.rest.gclient;
+
+import org.hl7.fhir.instance.model.IBaseResource;
+
+public interface IReadExecutable extends IClientExecutable, T>{
+
+ /**
+ * Send an "If-None-Match" header containing theVersion, which requests
+ * that the server return an "HTTP 301 Not Modified" if the newest version of the resource
+ * on the server has the same version as the version ID specified by theVersion.
+ * In this case, the client operation will perform the linked operation.
+ *
+ * @param theVersion The version ID (e.g. "123")
+ */
+ IReadIfNoneMatch ifVersionMatches(String theVersion);
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadIfNoneMatch.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadIfNoneMatch.java
new file mode 100644
index 00000000000..aec68276e3b
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadIfNoneMatch.java
@@ -0,0 +1,27 @@
+package ca.uhn.fhir.rest.gclient;
+
+import org.hl7.fhir.instance.model.IBaseResource;
+
+import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
+
+public interface IReadIfNoneMatch {
+
+ /**
+ * If the server responds with an HTTP 301 Not Modified,
+ * return the given instance.
+ */
+ IReadExecutable returnResource(T theInstance);
+
+ /**
+ * If the server responds with an HTTP 301 Not Modified,
+ * return null.
+ */
+ IReadExecutable returnNull();
+
+ /**
+ * If the server responds with an HTTP 301 Not Modified,
+ * throw a {@link NotModifiedException}.
+ */
+ IReadExecutable throwNotModifiedException();
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java
new file mode 100644
index 00000000000..1d36d4d8046
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java
@@ -0,0 +1,24 @@
+package ca.uhn.fhir.rest.gclient;
+
+import org.hl7.fhir.instance.model.IBaseResource;
+
+import ca.uhn.fhir.model.primitive.IdDt;
+
+public interface IReadTyped {
+
+ IReadExecutable withId(String theId);
+
+ IReadExecutable withIdAndVersion(String theId, String theVersion);
+
+ /**
+ * Search using an ID. Note that even if theId contains a base URL it will be
+ * ignored in favour of the base url for the given client. If you want to specify
+ * an absolute URL including a base and have that base used instead, use
+ * {@link #withUrl(IdDt)}
+ */
+ IReadExecutable withId(IdDt theId);
+
+ IReadExecutable withUrl(String theUrl);
+
+ IReadExecutable withUrl(IdDt theUrl);
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateExecutable.java
new file mode 100644
index 00000000000..6f84627bd84
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateExecutable.java
@@ -0,0 +1,28 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 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;
+
+public interface IUpdateExecutable extends IClientExecutable{
+
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java
index 3e44d0117a1..55226c2ff75 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java
@@ -21,11 +21,11 @@ package ca.uhn.fhir.rest.gclient;
*/
import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.api.MethodOutcome;
-public interface IUpdateTyped extends IClientExecutable {
+public interface IUpdateTyped extends IUpdateExecutable {
- IUpdateTyped withId(IdDt theId);
+ IUpdateExecutable withId(IdDt theId);
+
+ IUpdateExecutable withId(String theId);
- IUpdateTyped withId(String theId);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java
index 6666c882483..b84d89360ee 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java
@@ -180,7 +180,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding> theHeaders, IBaseResource resource) {
+ public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map> theHeaders, IBaseResource resource) {
List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE);
if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) {
String headerValue = lmHeaders.get(0);
@@ -132,7 +137,7 @@ public class MethodUtil {
if (resource instanceof IResource) {
InstantDt lmValue = new InstantDt(headerDateValue);
((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
- } else if (resource instanceof Resource){
+ } else if (resource instanceof Resource) {
((Resource) resource).getMeta().setLastUpdated(headerDateValue);
}
} catch (Exception e) {
@@ -148,6 +153,27 @@ public class MethodUtil {
}
}
+ IdDt existing = IdDt.of(resource);
+
+ List eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC);
+ String eTagVersion = null;
+ if (eTagHeaders != null && eTagHeaders.size() > 0) {
+ eTagVersion = parseETagValue(eTagHeaders.get(0));
+ }
+ if (isNotBlank(eTagVersion)) {
+ if (existing == null || existing.isEmpty()) {
+ if (theRequestedId != null) {
+ theRequestedId.withVersion(eTagVersion).applyTo(resource);
+ }
+ } else if (existing.hasVersionIdPart() == false) {
+ existing.withVersion(eTagVersion).applyTo(resource);
+ }
+ } else if (existing == null || existing.isEmpty()) {
+ if (theRequestedId != null) {
+ theRequestedId.applyTo(resource);
+ }
+ }
+
List categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC);
if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) {
TagList tagList = new TagList();
@@ -165,6 +191,27 @@ public class MethodUtil {
}
}
+ public static String parseETagValue(String value) {
+ String eTagVersion;
+ value = value.trim();
+ if (value.length() > 1) {
+ if (value.charAt(value.length() - 1) == '"') {
+ if (value.charAt(0) == '"') {
+ eTagVersion = value.substring(1, value.length() - 1);
+ } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') {
+ eTagVersion = value.substring(3, value.length() - 1);
+ } else {
+ eTagVersion = value;
+ }
+ } else {
+ eTagVersion = value;
+ }
+ } else {
+ eTagVersion = value;
+ }
+ return eTagVersion;
+ }
+
public static void parseTagValue(TagList tagList, String nextTagComplete) {
StringBuilder next = new StringBuilder(nextTagComplete);
parseTagValue(tagList, nextTagComplete, next);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java
index 53148047192..119a169aae9 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java
@@ -44,11 +44,13 @@ import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
public class ReadMethodBinding extends BaseResourceReturningMethodBinding implements IClientResponseHandlerHandlesBinary
*/
public class NotImplementedOperationException extends BaseServerResponseException {
-public static final int STATUS_CODE = Constants.STATUS_HTTP_501_NOT_IMPLEMENTED;
- private static final long serialVersionUID = 1L;
+ public static final int STATUS_CODE = Constants.STATUS_HTTP_501_NOT_IMPLEMENTED;
+ private static final long serialVersionUID = 1L;
+
+ public NotImplementedOperationException(String theMessage) {
+ super(STATUS_CODE, theMessage);
+ }
- public NotImplementedOperationException(String theMessage) {
- super(STATUS_CODE, theMessage);
- }
-
/**
* Constructor
*
* @param theMessage
* The message
- * @param theOperationOutcome The OperationOutcome resource to return to the client
+ * @param theOperationOutcome
+ * The OperationOutcome resource to return to the client
*/
public NotImplementedOperationException(String theMessage, BaseOperationOutcome theOperationOutcome) {
super(STATUS_CODE, theMessage, theOperationOutcome);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/NotModifiedException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/NotModifiedException.java
new file mode 100644
index 00000000000..eb9a0d99639
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/NotModifiedException.java
@@ -0,0 +1,57 @@
+package ca.uhn.fhir.rest.server.exceptions;
+
+import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
+import ca.uhn.fhir.rest.server.Constants;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 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%
+ */
+
+/**
+ * This Represents an HTTP 301 Not Modified response, which means the resource has not
+ * changed since the last version the client retrieved. This exception should only be used
+ * as a part of the ETag workflow.
+ *
+ *
+ * Note that a complete list of RESTful exceptions is available in the Package
+ * Summary.
+ *
+ */
+public class NotModifiedException extends BaseServerResponseException {
+
+ public static final int STATUS_CODE = Constants.STATUS_HTTP_304_NOT_MODIFIED;
+ private static final long serialVersionUID = 1L;
+
+ public NotModifiedException(String theMessage) {
+ super(STATUS_CODE, theMessage);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param theMessage
+ * The message
+ * @param theOperationOutcome
+ * The OperationOutcome resource to return to the client
+ */
+ public NotModifiedException(String theMessage, BaseOperationOutcome theOperationOutcome) {
+ super(STATUS_CODE, theMessage, theOperationOutcome);
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/PreconditionFailedException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/PreconditionFailedException.java
new file mode 100644
index 00000000000..f2fc28bf454
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/PreconditionFailedException.java
@@ -0,0 +1,53 @@
+package ca.uhn.fhir.rest.server.exceptions;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 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.model.base.resource.BaseOperationOutcome;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.server.Constants;
+
+/**
+ * Represents an HTTP 412 Precondition Failed response. This exception
+ * should be thrown for an {@link Update} operation if that operation requires a version to
+ * be specified in an HTTP header, and none was.
+ */
+@SuppressWarnings("deprecation")
+public class PreconditionFailedException extends ResourceVersionNotSpecifiedException {
+ @SuppressWarnings("hiding")
+ public static final int STATUS_CODE = Constants.STATUS_HTTP_412_PRECONDITION_FAILED;
+ private static final long serialVersionUID = 1L;
+
+ public PreconditionFailedException(String error) {
+ super(STATUS_CODE, error);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param theMessage
+ * The message
+ * @param theOperationOutcome The OperationOutcome resource to return to the client
+ */
+ public PreconditionFailedException(String theMessage, BaseOperationOutcome theOperationOutcome) {
+ super(STATUS_CODE, theMessage, theOperationOutcome);
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceVersionNotSpecifiedException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceVersionNotSpecifiedException.java
index 32ceed2c6a7..12f196368ab 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceVersionNotSpecifiedException.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceVersionNotSpecifiedException.java
@@ -25,9 +25,8 @@ import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.server.Constants;
/**
- * Represents an HTTP 412 Precondition Failed response. This exception
- * should be thrown for an {@link Update} operation if that operation requires a version to
- * be specified in an HTTP header, and none was.
+ * @deprecated Use {@link PreconditionFailedException} instead - This exception is
+ * strangely named and will be removed at some point.
*/
public class ResourceVersionNotSpecifiedException extends BaseServerResponseException {
public static final int STATUS_CODE = Constants.STATUS_HTTP_412_PRECONDITION_FAILED;
@@ -48,4 +47,19 @@ public class ResourceVersionNotSpecifiedException extends BaseServerResponseExce
super(STATUS_CODE, theMessage, theOperationOutcome);
}
+ public ResourceVersionNotSpecifiedException(int theStatusCode, String error) {
+ super(theStatusCode, error);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param theMessage
+ * The message
+ * @param theOperationOutcome The OperationOutcome resource to return to the client
+ */
+ public ResourceVersionNotSpecifiedException(int theStatusCode, String theMessage, BaseOperationOutcome theOperationOutcome) {
+ super(theStatusCode, theMessage, theOperationOutcome);
+ }
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ICallable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ICallable.java
new file mode 100644
index 00000000000..34307d00ab7
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ICallable.java
@@ -0,0 +1,7 @@
+package ca.uhn.fhir.util;
+
+public interface ICallable {
+
+ T call();
+
+}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
index bf4786fb6af..4ef412c5b23 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
@@ -13,6 +13,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProvider;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
@@ -57,6 +58,7 @@ public class TestRestfulServer extends RestfulServer {
List beans;
JpaSystemProvider systemProvider;
IFhirSystemDao systemDao;
+ ETagSupportEnum etagSupport;
switch (fhirVersionParam.trim().toUpperCase()) {
case "DSTU":
case "DSTU1":
@@ -64,17 +66,21 @@ public class TestRestfulServer extends RestfulServer {
beans = myAppCtx.getBean("myResourceProvidersDstu1", List.class);
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProvider.class);
systemDao = myAppCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class);
+ etagSupport = ETagSupportEnum.DISABLED;
break;
case "DEV":
setFhirContext(FhirContext.forDev());
beans = myAppCtx.getBean("myResourceProvidersDev", List.class);
systemProvider = myAppCtx.getBean("mySystemProviderDev", JpaSystemProvider.class);
systemDao = myAppCtx.getBean("mySystemDaoDev", IFhirSystemDao.class);
+ etagSupport = ETagSupportEnum.ENABLED;
break;
default:
throw new ServletException("Unknown FHIR version specified in init-param[FhirVersion]: " + fhirVersionParam);
}
+ setETagSupport(etagSupport);
+
FhirContext ctx = getFhirContext();
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
@@ -107,18 +113,4 @@ public class TestRestfulServer extends RestfulServer {
}
- @Override
- public void destroy() {
- super.destroy();
-
-// myAppCtx.close();
-//
-// try {
-// ourLog.info("Shutting down derby");
-// DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";shutdown=true");
-// } catch (Exception e) {
-// ourLog.info("Failed to create database: {}",e.getMessage());
-// }
- }
-
}
diff --git a/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/rest/client/ETagClientTest.java b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/rest/client/ETagClientTest.java
new file mode 100644
index 00000000000..557fca9a68c
--- /dev/null
+++ b/hapi-fhir-structures-dev/src/test/java/ca/uhn/fhir/rest/client/ETagClientTest.java
@@ -0,0 +1,300 @@
+package ca.uhn.fhir.rest.client;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.ReaderInputStream;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicStatusLine;
+import org.hamcrest.core.StringContains;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
+
+import com.google.common.base.Preconditions;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.api.TagList;
+import ca.uhn.fhir.model.dev.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.InstantDt;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
+
+/**
+ * Created by dsotnikov on 2/25/2014.
+ */
+public class ETagClientTest {
+
+ private static FhirContext myCtx;
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class);
+ private HttpClient myHttpClient;
+
+ private HttpResponse myHttpResponse;
+
+ @Before
+ public void before() {
+
+ myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
+ myCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
+
+ myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
+ }
+
+ private String extractBody(ArgumentCaptor capt, int count) throws IOException {
+ String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8");
+ return body;
+ }
+
+
+ private String getResourceResult() {
+ //@formatter:off
+ String msg =
+ ""
+ + "
+ HAPI provides support for
+ HTTP ETags, which are
+ a standard way of providing faster reads when content has not changed and
+ optimistic locking for updates.
+
+
+
+
+
+
+
+ ETag features are added simply by adding fluent method calls to the
+ client method chain, as shown in the following examples.
+
+
+
+
+
+
+ To notify the server that it should return an HTTP 301 Not Modified
+ if the content has not changed, add an ifVersionMatches(foo).[operation]
+ invocation.
+
+
+
+
+
+
+
+
+ This method will add the following header to the request:
+
+
+
+
+
+
+
+
+
+ To implement version aware updates, specify a version in the
+ request. This will notify the server that it should only update the
+ resource on the server if the version matches the given version. This
+ is useful to prevent two clients from attempting to modify the
+ resource at the same time, and having one client's updates overwrite
+ the other's.
+
+
+
+
+
+
+
+
+ The following header will be added to the request as a part of this
+ interaction.
+
+
+
+
+
+
+
+
+
+
+ As of HAPI 0.9, ETag support is automatically enabled in the RESTful server.
+ This has the following effects:
+
+
+
+ Read/VRead
+ method responses will include an
+ ETag header, noting the version
+ being returned.
+
+
+ If an incoming Read method includes an If-None-Match header with
+ the same version as the latest version being returned, the server will automatically
+ return an HTTP 304 Not Modified instead of returning the
+ resource body.
+
+
+
+
+
+
+ To disable ETag support, simply invoke the
+ setETagSupport method, as in the following example.
+