Implementing ETag support
This commit is contained in:
parent
31d61100db
commit
fd8ba68e62
|
@ -1,61 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>0.8-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-base-examples</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HAPI FHIR - Examples (for site)</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-base</artifactId>
|
||||
<version>0.8-SNAPSHOT</version>
|
||||
<<<<<<< HEAD:hapi-fhir-base/examples/pom.xml
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
>>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:examples/pom.xml.orig
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu</artifactId>
|
||||
<version>0.8-SNAPSHOT</version>
|
||||
<<<<<<< HEAD:hapi-fhir-base/examples/pom.xml
|
||||
=======
|
||||
>>>>>>> versions
|
||||
>>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:examples/pom.xml.orig
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit_version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<Date> {
|
||||
|
||||
/*
|
||||
* Add any new formatters to the static block below!!
|
||||
*/
|
||||
private static final List<FastDateFormat> 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<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
|
||||
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 <code>true</code> if this object represents a date that is today's date
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* if {@link #getValue()} returns <code>null</code>
|
||||
*/
|
||||
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:
|
||||
* <ul>
|
||||
* <li>{@link Calendar#SECOND}
|
||||
* <li>{@link Calendar#DAY_OF_MONTH}
|
||||
* <li>{@link Calendar#MONTH}
|
||||
* <li>{@link Calendar#YEAR}
|
||||
* </ul>
|
||||
*
|
||||
* @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<FastDateFormat> getFormatters() {
|
||||
return ourFormatters;
|
||||
=======
|
||||
/**
|
||||
* Sets the precision for this datatype using field values from {@link Calendar}. Valid values are:
|
||||
* <ul>
|
||||
* <li>{@link Calendar#SECOND}
|
||||
* <li>{@link Calendar#DAY_OF_MONTH}
|
||||
* <li>{@link Calendar#MONTH}
|
||||
* <li>{@link Calendar#YEAR}
|
||||
* </ul>
|
||||
*
|
||||
* @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
|
||||
}
|
||||
|
||||
}
|
|
@ -451,6 +451,11 @@ public class IdDt implements IPrimitiveDatatype<String> {
|
|||
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<String> {
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* <b>Description</b>: 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* regex: [a-z0-9\-\.]{1,36}
|
||||
* </p>
|
||||
*/
|
||||
@DatatypeDef(name = "id")
|
||||
public class IdDt implements IPrimitiveDatatype<String> {
|
||||
|
||||
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).
|
||||
*
|
||||
* <p>
|
||||
* <b>Description</b>: 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* regex: [a-z0-9\-\.]{1,36}
|
||||
* </p>
|
||||
*/
|
||||
@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 <code>http://example.com/fhir/Patient/123</code> the base URL would be
|
||||
* <code>http://example.com/fhir</code>.
|
||||
* <p>
|
||||
* This method may return null if the ID contains no base (e.g. "Patient/123")
|
||||
* </p>
|
||||
*/
|
||||
public String getBaseUrl() {
|
||||
return myBaseUrl;
|
||||
}
|
||||
|
||||
public String getIdPart() {
|
||||
return myUnqualifiedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unqualified portion of this ID as a big decimal, or <code>null</code> 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 <code>null</code> 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <code>this</code> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>Description</b>: 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* regex: [a-z0-9\-\.]{1,36}
|
||||
* </p>
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IdDt setValue(String theValue) throws DataFormatException {
|
||||
// TODO: add validation
|
||||
myValue = theValue;
|
||||
myHaveComponentParts = false;
|
||||
if (StringUtils.isBlank(theValue)) {
|
||||
myValue = null;
|
||||
myUnqualifiedId = null;
|
||||
myUnqualifiedVersionId = null;
|
||||
myResourceType = null;
|
||||
} else {
|
||||
int vidIndex = theValue.indexOf("/_history/");
|
||||
int idIndex;
|
||||
if (vidIndex != -1) {
|
||||
myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
|
||||
idIndex = theValue.lastIndexOf('/', vidIndex - 1);
|
||||
myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
|
||||
} else {
|
||||
idIndex = theValue.lastIndexOf('/');
|
||||
myUnqualifiedId = theValue.substring(idIndex + 1);
|
||||
myUnqualifiedVersionId = null;
|
||||
}
|
||||
|
||||
myBaseUrl = null;
|
||||
if (idIndex <= 0) {
|
||||
myResourceType = null;
|
||||
} else {
|
||||
int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
|
||||
if (typeIndex == -1) {
|
||||
myResourceType = theValue.substring(0, idIndex);
|
||||
} else {
|
||||
myResourceType = theValue.substring(typeIndex + 1, idIndex);
|
||||
|
||||
if (typeIndex > 4) {
|
||||
myBaseUrl = theValue.substring(0, typeIndex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value
|
||||
*
|
||||
* <p>
|
||||
* <b>Description</b>: 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* regex: [a-z0-9\-\.]{1,36}
|
||||
* </p>
|
||||
*/
|
||||
@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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<BaseConformance> conformance = (Class<BaseConformance>) myContext.getResourceDefinition("Conformance").getImplementingClass();
|
||||
|
||||
|
||||
ResourceResponseHandler<? extends BaseConformance> binding = new ResourceResponseHandler<BaseConformance>(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 extends IBaseResource> T read(final Class<T> 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<T> binding = new ResourceResponseHandler<T>(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 extends IResource> T vread(final Class<T> theType, IdDt theId) {
|
||||
public <T extends IBaseResource> T vread(final Class<T> 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 extends IBaseResource> T doReadOrVRead(final Class<T> theType, IdDt theId, boolean theVRead, ICallable<T> 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<T> binding = new ResourceResponseHandler<T>(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 extends IResource> T vread(Class<T> theType, String theId, String theVersionId) {
|
||||
public <T extends IBaseResource> T vread(Class<T> 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<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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 <T extends IBaseResource> IReadTyped<T> resource(Class<T> 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<IBaseResource> 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<IGetTags, TagList> implements IGetTags {
|
||||
|
||||
private String myId;
|
||||
|
@ -776,8 +916,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {
|
||||
|
||||
@Override
|
||||
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<TagList> {
|
||||
|
||||
@Override
|
||||
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<IUpdateTyped, MethodOutcome> implements IUpdate, IUpdateTyped {
|
||||
private class UpdateInternal extends BaseClientExecutable<IUpdateExecutable, MethodOutcome> 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");
|
||||
}
|
||||
|
|
|
@ -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 <code>null<code> to search for history across all resources
|
||||
* The type of resource to return the history for, or
|
||||
* <code>null<code> to search for history across all resources
|
||||
* @param theId
|
||||
* The ID of the resource to return the history for, or <code>null</code> to search for all resource instances. Note that if this param is not null, <code>theType</code> must also not
|
||||
* be null
|
||||
* The ID of the resource to return the history for, or <code>null</code> to search for all resource
|
||||
* instances. Note that if this param is not null, <code>theType</code> 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
|
||||
*/
|
||||
<T extends IResource> Bundle history(Class<T> 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 <code>null<code> to search for history across all resources
|
||||
* The type of resource to return the history for, or
|
||||
* <code>null<code> to search for history across all resources
|
||||
* @param theId
|
||||
* The ID of the resource to return the history for, or <code>null</code> to search for all resource instances. Note that if this param is not null, <code>theType</code> must also not
|
||||
* be null
|
||||
* The ID of the resource to return the history for, or <code>null</code> to search for all resource
|
||||
* instances. Note that if this param is not null, <code>theType</code> 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
|
||||
*/
|
||||
<T extends IResource> Bundle history(Class<T> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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 extends IBaseResource> T read(Class<T> 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 <code>true</code>, 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 <code>true</code>, 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 (<b>these will not be fully populated</b>) containing IDs and other {@link IResource#getResourceMetadata() metadata}
|
||||
* @return A list of resource stubs (<b>these will not be fully populated</b>) containing IDs and other
|
||||
* {@link IResource#getResourceMetadata() metadata}
|
||||
* @deprecated Use {@link #transaction()}
|
||||
*
|
||||
*/
|
||||
List<IResource> transaction(List<IResource> 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 <code>theId</code> 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 <code>theId</code> to contain a
|
||||
* resource ID as well as a version ID, and will fail if it does not.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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 extends IResource> T vread(Class<T> theType, IdDt theId);
|
||||
<T extends IBaseResource> T vread(Class<T> theType, IdDt theId);
|
||||
|
||||
/**
|
||||
* Implementation of the "instance vread" method.
|
||||
|
@ -325,6 +343,6 @@ public interface IGenericClient {
|
|||
* The version ID
|
||||
* @return The resource
|
||||
*/
|
||||
<T extends IResource> T vread(Class<T> theType, String theId, String theVersionId);
|
||||
<T extends IBaseResource> T vread(Class<T> theType, String theId, String theVersionId);
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
*/
|
||||
<T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType);
|
||||
|
||||
/**
|
||||
* Construct a read for the given resource type (e.g. "Patient")
|
||||
*/
|
||||
IReadTyped<IBaseResource> resource(String theResourceType);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.rest.gclient;
|
||||
|
||||
import org.hl7.fhir.instance.model.IBaseResource;
|
||||
|
||||
public interface IReadExecutable<T extends IBaseResource> extends IClientExecutable<IReadExecutable<T>, T>{
|
||||
|
||||
/**
|
||||
* Send an "If-None-Match" header containing <code>theVersion</code>, 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 <code>theVersion</code>.
|
||||
* In this case, the client operation will perform the linked operation.
|
||||
* <p>
|
||||
* See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_etag.html">ETag Documentation</a>
|
||||
* for more information.
|
||||
* </p>
|
||||
* @param theVersion The version ID (e.g. "123")
|
||||
*/
|
||||
IReadIfNoneMatch<T> ifVersionMatches(String theVersion);
|
||||
|
||||
}
|
|
@ -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<T extends IBaseResource> {
|
||||
|
||||
/**
|
||||
* If the server responds with an <code>HTTP 301 Not Modified</code>,
|
||||
* return the given instance.
|
||||
*/
|
||||
IReadExecutable<T> returnResource(T theInstance);
|
||||
|
||||
/**
|
||||
* If the server responds with an <code>HTTP 301 Not Modified</code>,
|
||||
* return <code>null</code>.
|
||||
*/
|
||||
IReadExecutable<T> returnNull();
|
||||
|
||||
/**
|
||||
* If the server responds with an <code>HTTP 301 Not Modified</code>,
|
||||
* throw a {@link NotModifiedException}.
|
||||
*/
|
||||
IReadExecutable<T> throwNotModifiedException();
|
||||
|
||||
}
|
|
@ -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<T extends IBaseResource> {
|
||||
|
||||
IReadExecutable<T> withId(String theId);
|
||||
|
||||
IReadExecutable<T> 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<T> withId(IdDt theId);
|
||||
|
||||
IReadExecutable<T> withUrl(String theUrl);
|
||||
|
||||
IReadExecutable<T> withUrl(IdDt theUrl);
|
||||
}
|
|
@ -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<IUpdateExecutable, MethodOutcome>{
|
||||
|
||||
|
||||
}
|
|
@ -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<IUpdateTyped, MethodOutcome> {
|
||||
public interface IUpdateTyped extends IUpdateExecutable {
|
||||
|
||||
IUpdateTyped withId(IdDt theId);
|
||||
IUpdateExecutable withId(IdDt theId);
|
||||
|
||||
IUpdateExecutable withId(String theId);
|
||||
|
||||
IUpdateTyped withId(String theId);
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
resource = parser.parseResource(theResponseReader);
|
||||
}
|
||||
|
||||
MethodUtil.parseClientRequestResourceHeaders(theHeaders, resource);
|
||||
MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource);
|
||||
|
||||
switch (getMethodReturnType()) {
|
||||
case BUNDLE:
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.hl7.fhir.instance.model.Resource.ResourceMetaComponent;
|
|||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -106,13 +107,17 @@ public class MethodUtil {
|
|||
}
|
||||
|
||||
if (theId.hasVersionIdPart()) {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +127,7 @@ public class MethodUtil {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static void parseClientRequestResourceHeaders(Map<String, List<String>> theHeaders, IBaseResource resource) {
|
||||
public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map<String, List<String>> theHeaders, IBaseResource resource) {
|
||||
List<String> 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<String> 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<String> 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);
|
||||
|
|
|
@ -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<Object> {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
|
||||
|
@ -143,7 +145,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
if (myVersionIdIndex == null) {
|
||||
String resourceName = getResourceName();
|
||||
if (id.hasVersionIdPart()) {
|
||||
retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()));
|
||||
retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()), resourceName);
|
||||
} else {
|
||||
retVal = createReadInvocation(id, resourceName);
|
||||
}
|
||||
|
@ -151,7 +153,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
IdDt vid = ((IdDt) theArgs[myVersionIdIndex]);
|
||||
String resourceName = getResourceName();
|
||||
|
||||
retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()));
|
||||
retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()), resourceName);
|
||||
}
|
||||
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
|
@ -163,8 +165,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
|
||||
byte[] contents = IOUtils.toByteArray(theResponseReader);
|
||||
Binary resource = new Binary(theResponseMimeType, contents);
|
||||
|
||||
|
@ -190,8 +191,25 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
}
|
||||
|
||||
Object response = invokeServerMethod(theMethodParams);
|
||||
IBundleProvider retVal = toResourceList(response);
|
||||
|
||||
return toResourceList(response);
|
||||
if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {
|
||||
String ifNoneMatch = ((Request)theRequest).getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC);
|
||||
if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) {
|
||||
List<IResource> responseResources = retVal.getResources(0, 1);
|
||||
IResource responseResource = responseResources.get(0);
|
||||
|
||||
ifNoneMatch = MethodUtil.parseETagValue(ifNoneMatch);
|
||||
if (responseResource.getId() != null && responseResource.getId().hasVersionIdPart()) {
|
||||
if (responseResource.getId().getVersionIdPart().equals(ifNoneMatch)) {
|
||||
ourLog.debug("Returning HTTP 301 because request specified {}={}", Constants.HEADER_IF_NONE_MATCH, ifNoneMatch);
|
||||
throw new NotModifiedException("Not Modified");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -215,8 +233,8 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart()).getValue());
|
||||
}
|
||||
|
||||
public static HttpGetClientInvocation createVReadInvocation(IdDt theId) {
|
||||
return new HttpGetClientInvocation(theId.getValue());
|
||||
public static HttpGetClientInvocation createVReadInvocation(IdDt theId, String theResourceName) {
|
||||
return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart(), theId.getVersionIdPart()).getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,21 +25,25 @@ import java.util.Map;
|
|||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
||||
public class RequestDetails {
|
||||
|
||||
private String myCompartmentName;
|
||||
private String myCompleteUrl;
|
||||
private IdDt myId;
|
||||
private OtherOperationTypeEnum myOtherOperationType;
|
||||
private Map<String, String[]> myParameters;
|
||||
private String myResourceName;
|
||||
private RestfulOperationTypeEnum myResourceOperationType;
|
||||
private RestfulServer myServer;
|
||||
private RestfulOperationSystemEnum mySystemOperationType;
|
||||
private String myCompleteUrl;
|
||||
|
||||
public String getCompartmentName() {
|
||||
return myCompartmentName;
|
||||
}
|
||||
public String getCompleteUrl() {
|
||||
return myCompleteUrl;
|
||||
}
|
||||
|
||||
public IdDt getId() {
|
||||
return myId;
|
||||
|
@ -61,6 +65,10 @@ public class RequestDetails {
|
|||
return myResourceOperationType;
|
||||
}
|
||||
|
||||
public RestfulServer getServer() {
|
||||
return myServer;
|
||||
}
|
||||
|
||||
public RestfulOperationSystemEnum getSystemOperationType() {
|
||||
return mySystemOperationType;
|
||||
}
|
||||
|
@ -69,6 +77,10 @@ public class RequestDetails {
|
|||
myCompartmentName = theCompartmentName;
|
||||
}
|
||||
|
||||
public void setCompleteUrl(String theCompleteUrl) {
|
||||
myCompleteUrl = theCompleteUrl;
|
||||
}
|
||||
|
||||
public void setId(IdDt theId) {
|
||||
myId = theId;
|
||||
}
|
||||
|
@ -89,16 +101,12 @@ public class RequestDetails {
|
|||
myResourceOperationType = theResourceOperationType;
|
||||
}
|
||||
|
||||
public void setServer(RestfulServer theServer) {
|
||||
myServer = theServer;
|
||||
}
|
||||
|
||||
public void setSystemOperationType(RestfulOperationSystemEnum theSystemOperationType) {
|
||||
mySystemOperationType = theSystemOperationType;
|
||||
}
|
||||
|
||||
public String getCompleteUrl() {
|
||||
return myCompleteUrl;
|
||||
}
|
||||
|
||||
public void setCompleteUrl(String theCompleteUrl) {
|
||||
myCompleteUrl = theCompleteUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -72,19 +72,30 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
|
|||
* Content-Location header, but we allow it in the PUT URL as well..
|
||||
*/
|
||||
String locationHeader = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_LOCATION);
|
||||
IdDt id = new IdDt(locationHeader);
|
||||
if (isNotBlank(id.getResourceType())) {
|
||||
if (!getResourceName().equals(id.getResourceType())) {
|
||||
throw new InvalidRequestException("Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
|
||||
IdDt id = theRequest.getId();
|
||||
if (isNotBlank(locationHeader)) {
|
||||
id = new IdDt(locationHeader);
|
||||
if (isNotBlank(id.getResourceType())) {
|
||||
if (!getResourceName().equals(id.getResourceType())) {
|
||||
throw new InvalidRequestException("Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String ifMatchValue = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_MATCH);
|
||||
if (isNotBlank(ifMatchValue)) {
|
||||
ifMatchValue = MethodUtil.parseETagValue(ifMatchValue);
|
||||
if (id != null && id.hasVersionIdPart() == false) {
|
||||
id = id.withVersion(ifMatchValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (theRequest.getId() != null && theRequest.getId().hasVersionIdPart() == false) {
|
||||
if (id != null && id.hasVersionIdPart()) {
|
||||
theRequest.setId(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isNotBlank(locationHeader)) {
|
||||
MethodOutcome mo = new MethodOutcome();
|
||||
parseContentLocation(mo, getResourceName(), locationHeader);
|
||||
|
@ -114,7 +125,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
|
|||
}
|
||||
FhirContext context = getContext();
|
||||
|
||||
HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null,idDt, context);
|
||||
HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null, idDt, context);
|
||||
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
IParameter nextParam = getParameters().get(idx);
|
||||
|
@ -124,28 +135,13 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
|
||||
if (super.incomingServerRequestMatchesMethod(theRequest)) {
|
||||
if (myVersionIdParameterIndex != null) {
|
||||
if (theRequest.getVersionId() == null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (theRequest.getVersionId() != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
* @Override public boolean incomingServerRequestMatchesMethod(Request theRequest) { if
|
||||
* (super.incomingServerRequestMatchesMethod(theRequest)) { if (myVersionIdParameterIndex != null) { if
|
||||
* (theRequest.getVersionId() == null) { return false; } } else { if (theRequest.getVersionId() != null) { return
|
||||
* false; } } return true; } else { return false; } }
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected Set<RequestType> provideAllowableRequestTypes() {
|
||||
return Collections.singleton(RequestType.PUT);
|
||||
|
|
|
@ -44,13 +44,12 @@ public class Constants {
|
|||
public static final Map<String, EncodingEnum> FORMAT_VAL_TO_ENCODING;
|
||||
public static final Set<String> FORMAT_VAL_XML;
|
||||
public static final String FORMAT_XML = "xml";
|
||||
public static final String HEADER_SUFFIX_CT_UTF_8 = "; charset=UTF-8";
|
||||
public static final String HEADER_ACCEPT = "Accept";
|
||||
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
|
||||
public static final String HEADER_AUTHORIZATION = "Authorization";
|
||||
public static final String HEADER_CATEGORY = "Category";
|
||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic ";
|
||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BEARER = "Bearer ";
|
||||
public static final String HEADER_CATEGORY = "Category";
|
||||
public static final String HEADER_CATEGORY_LC = HEADER_CATEGORY.toLowerCase();
|
||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
|
@ -61,10 +60,17 @@ public class Constants {
|
|||
public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
|
||||
public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
|
||||
public static final String HEADER_CORS_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
|
||||
public static final String HEADER_ETAG = "ETag";
|
||||
public static final String HEADER_ETAG_LC = HEADER_ETAG.toLowerCase();
|
||||
public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String HEADER_IF_NONE_MATCH_LC = HEADER_IF_NONE_MATCH.toLowerCase();
|
||||
public static final String HEADER_IF_MATCH = "If-Match";
|
||||
public static final String HEADER_IF_MATCH_LC = HEADER_IF_MATCH.toLowerCase();
|
||||
public static final String HEADER_LAST_MODIFIED = "Last-Modified";
|
||||
public static final String HEADER_LAST_MODIFIED_LOWERCASE = HEADER_LAST_MODIFIED.toLowerCase();
|
||||
public static final String HEADER_LOCATION = "Location";
|
||||
public static final String HEADER_LOCATION_LC = HEADER_LOCATION.toLowerCase();
|
||||
public static final String HEADER_SUFFIX_CT_UTF_8 = "; charset=UTF-8";
|
||||
public static final String HEADERVALUE_CORS_ALLOW_METHODS_ALL = "GET, POST, PUT, DELETE, OPTIONS";
|
||||
public static final String OPENSEARCH_NS_OLDER = "http://purl.org/atompub/tombstones/1.0";
|
||||
public static final String PARAM_COUNT = "_count";
|
||||
|
@ -91,6 +97,7 @@ public class Constants {
|
|||
public static final int STATUS_HTTP_200_OK = 200;
|
||||
public static final int STATUS_HTTP_201_CREATED = 201;
|
||||
public static final int STATUS_HTTP_204_NO_CONTENT = 204;
|
||||
public static final int STATUS_HTTP_304_NOT_MODIFIED = 304;
|
||||
public static final int STATUS_HTTP_400_BAD_REQUEST = 400;
|
||||
public static final int STATUS_HTTP_401_CLIENT_UNAUTHORIZED = 401;
|
||||
public static final int STATUS_HTTP_404_NOT_FOUND = 404;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
/*
|
||||
* #%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%
|
||||
*/
|
||||
|
||||
/**
|
||||
* RESTful server behaviour for automatically adding profile tags
|
||||
*
|
||||
* @see RestfulServer#setETagSupport(ETagSupportEnum)
|
||||
*/
|
||||
public enum ETagSupportEnum {
|
||||
/**
|
||||
* Send ETag headers
|
||||
*/
|
||||
ENABLED,
|
||||
|
||||
/**
|
||||
* Do not send ETag headers
|
||||
*/
|
||||
DISABLED
|
||||
}
|
|
@ -39,12 +39,14 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
|||
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;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.util.VersionUtil;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.http.client.utils.DateUtils;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
|
@ -68,10 +70,15 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
public class RestfulServer extends HttpServlet {
|
||||
|
||||
/**
|
||||
* Default setting for {@link #setETagSupport(ETagSupportEnum)ETag Support}: {@link ETagSupportEnum#ENABLED}
|
||||
*/
|
||||
public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private AddProfileTagEnum myAddProfileTag;
|
||||
private ETagSupportEnum myETagSupport = DEFAULT_ETAG_SUPPORT;
|
||||
private FhirContext myFhirContext;
|
||||
private String myImplementationDescription;
|
||||
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
|
||||
|
@ -87,6 +94,7 @@ public class RestfulServer extends HttpServlet {
|
|||
/** This is configurable but by default we just use HAPI version */
|
||||
private String myServerVersion = VersionUtil.getVersion();
|
||||
private boolean myStarted;
|
||||
|
||||
private boolean myUseBrowserFriendlyContentTypes;
|
||||
|
||||
/**
|
||||
|
@ -104,7 +112,8 @@ public class RestfulServer extends HttpServlet {
|
|||
/**
|
||||
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
|
||||
* <p>
|
||||
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
|
||||
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
|
||||
* inadvertantly disabling functionality.
|
||||
* </p>
|
||||
*/
|
||||
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
||||
|
@ -117,6 +126,15 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (getResourceProviders() != null) {
|
||||
for (IResourceProvider iResourceProvider : getResourceProviders()) {
|
||||
invokeDestroy(iResourceProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
|
||||
|
@ -262,32 +280,6 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
private void invokeDestroy(Object theProvider) {
|
||||
Class<?> clazz = theProvider.getClass();
|
||||
invokeDestroy(theProvider, clazz);
|
||||
}
|
||||
|
||||
private void invokeDestroy(Object theProvider, Class<?> clazz) {
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
Destroy destroy = m.getAnnotation(Destroy.class);
|
||||
if (destroy != null) {
|
||||
try {
|
||||
m.invoke(theProvider);
|
||||
} catch (IllegalAccessException e) {
|
||||
ourLog.error("Exception occurred in destroy ", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
ourLog.error("Exception occurred in destroy ", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> supertype = clazz.getSuperclass();
|
||||
if (!Object.class.equals(supertype)) {
|
||||
invokeDestroy(theProvider, supertype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setting for automatically adding profile tags
|
||||
*
|
||||
|
@ -298,8 +290,15 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
|
||||
* creating their own.
|
||||
* Returns the server support for ETags (will not be <code>null</code>). Default is {@link #DEFAULT_ETAG_SUPPORT}
|
||||
*/
|
||||
public ETagSupportEnum getETagSupport() {
|
||||
return myETagSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
|
||||
* providers should generally use this context if one is needed, as opposed to creating their own.
|
||||
*/
|
||||
public FhirContext getFhirContext() {
|
||||
return myFhirContext;
|
||||
|
@ -341,16 +340,29 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
|
||||
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||
*/
|
||||
public IServerAddressStrategy getServerAddressStrategy() {
|
||||
return myServerAddressStrategy;
|
||||
}
|
||||
|
||||
public String getServerBaseForRequest(HttpServletRequest theRequest) {
|
||||
String fhirServerBase;
|
||||
fhirServerBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest);
|
||||
|
||||
if (fhirServerBase.endsWith("/")) {
|
||||
fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1);
|
||||
}
|
||||
return fhirServerBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
|
||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
||||
* (metadata) statement.
|
||||
* <p>
|
||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
|
||||
* if you do not wish to export a conformance statement.
|
||||
* </p>
|
||||
*/
|
||||
public Object getServerConformanceProvider() {
|
||||
|
@ -358,7 +370,8 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
||||
* but can be helpful to set with something appropriate.
|
||||
*
|
||||
* @see RestfulServer#setServerName(String)
|
||||
*/
|
||||
|
@ -371,7 +384,8 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
||||
* only, but can be helpful to set with something appropriate.
|
||||
*/
|
||||
public String getServerVersion() {
|
||||
return myServerVersion;
|
||||
|
@ -410,8 +424,7 @@ public class RestfulServer extends HttpServlet {
|
|||
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
|
||||
boolean respondGzip = theRequest.isRespondGzip();
|
||||
|
||||
Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser,
|
||||
narrativeMode, start, count, thePagingAction);
|
||||
Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction);
|
||||
|
||||
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||
IServerInterceptor next = getInterceptors().get(i);
|
||||
|
@ -437,7 +450,7 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
String fhirServerBase = null;
|
||||
boolean requestIsBrowser = requestIsBrowser(theRequest);
|
||||
RequestDetails requestDetails=null;
|
||||
RequestDetails requestDetails = null;
|
||||
try {
|
||||
|
||||
String resourceName = null;
|
||||
|
@ -563,6 +576,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
Request r = new Request();
|
||||
r.setServer(this);
|
||||
r.setResourceName(resourceName);
|
||||
r.setId(id);
|
||||
r.setOperation(operation);
|
||||
|
@ -613,6 +627,17 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
resourceMethod.invokeServer(this, r);
|
||||
|
||||
} catch (NotModifiedException e) {
|
||||
|
||||
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||
IServerInterceptor next = getInterceptors().get(i);
|
||||
if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
|
||||
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||
return;
|
||||
}
|
||||
}
|
||||
writeExceptionToResponse(theResponse, e);
|
||||
|
||||
} catch (AuthenticationException e) {
|
||||
|
||||
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||
|
@ -627,17 +652,13 @@ public class RestfulServer extends HttpServlet {
|
|||
// if request is coming from a browser, prompt the user to enter login credentials
|
||||
theResponse.setHeader("WWW-Authenticate", "BASIC realm=\"FHIR\"");
|
||||
}
|
||||
theResponse.setStatus(e.getStatusCode());
|
||||
addHeadersToResponse(theResponse);
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().write(e.getMessage());
|
||||
writeExceptionToResponse(theResponse, e);
|
||||
|
||||
} catch (Throwable e) {
|
||||
|
||||
/*
|
||||
* We have caught an exception while handling an incoming server request.
|
||||
* Start by notifying the interceptors..
|
||||
* We have caught an exception while handling an incoming server request. Start by notifying the
|
||||
* interceptors..
|
||||
*/
|
||||
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||
IServerInterceptor next = getInterceptors().get(i);
|
||||
|
@ -656,8 +677,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/*
|
||||
* Generate an OperationOutcome to return, unless the exception throw by
|
||||
* the resource provider had one
|
||||
* Generate an OperationOutcome to return, unless the exception throw by the resource provider had one
|
||||
*/
|
||||
if (oo == null) {
|
||||
try {
|
||||
|
@ -705,19 +725,10 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
public String getServerBaseForRequest(HttpServletRequest theRequest) {
|
||||
String fhirServerBase;
|
||||
fhirServerBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest);
|
||||
|
||||
if (fhirServerBase.endsWith("/")) {
|
||||
fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1);
|
||||
}
|
||||
return fhirServerBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
|
||||
* called immediately before beginning initialization of the restful server's internal init.
|
||||
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
|
||||
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
|
||||
* initialization of the restful server's internal init.
|
||||
*/
|
||||
@Override
|
||||
public final void init() throws ServletException {
|
||||
|
@ -732,12 +743,12 @@ public class RestfulServer extends HttpServlet {
|
|||
if (resourceProvider != null) {
|
||||
Map<String, IResourceProvider> typeToProvider = new HashMap<String, IResourceProvider>();
|
||||
for (IResourceProvider nextProvider : resourceProvider) {
|
||||
|
||||
|
||||
Class<? extends IResource> resourceType = nextProvider.getResourceType();
|
||||
if (resourceType == null) {
|
||||
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
|
||||
}
|
||||
|
||||
|
||||
String resourceName = myFhirContext.getResourceDefinition(resourceType).getName();
|
||||
if (typeToProvider.containsKey(resourceName)) {
|
||||
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
|
||||
|
@ -773,22 +784,40 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
|
||||
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
|
||||
* server being used.
|
||||
*/
|
||||
protected void initialize() throws ServletException {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (getResourceProviders() != null) {
|
||||
for (IResourceProvider iResourceProvider : getResourceProviders()) {
|
||||
invokeDestroy(iResourceProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void invokeDestroy(Object theProvider) {
|
||||
Class<?> clazz = theProvider.getClass();
|
||||
invokeDestroy(theProvider, clazz);
|
||||
}
|
||||
|
||||
public boolean isUseBrowserFriendlyContentTypes() {
|
||||
private void invokeDestroy(Object theProvider, Class<?> clazz) {
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
Destroy destroy = m.getAnnotation(Destroy.class);
|
||||
if (destroy != null) {
|
||||
try {
|
||||
m.invoke(theProvider);
|
||||
} catch (IllegalAccessException e) {
|
||||
ourLog.error("Exception occurred in destroy ", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
ourLog.error("Exception occurred in destroy ", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> supertype = clazz.getSuperclass();
|
||||
if (!Object.class.equals(supertype)) {
|
||||
invokeDestroy(theProvider, supertype);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUseBrowserFriendlyContentTypes() {
|
||||
return myUseBrowserFriendlyContentTypes;
|
||||
}
|
||||
|
||||
|
@ -803,8 +832,9 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
|
||||
* on the class of the resource(s) being returned.
|
||||
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
|
||||
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
|
||||
* being returned.
|
||||
*
|
||||
* @param theAddProfileTag
|
||||
* The behaviour enum (must not be null)
|
||||
|
@ -814,6 +844,20 @@ public class RestfulServer extends HttpServlet {
|
|||
myAddProfileTag = theAddProfileTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
|
||||
* {@link #DEFAULT_ETAG_SUPPORT}
|
||||
*
|
||||
* @param theETagSupport
|
||||
* The ETag support mode
|
||||
*/
|
||||
public void setETagSupport(ETagSupportEnum theETagSupport) {
|
||||
if (theETagSupport == null) {
|
||||
throw new NullPointerException("theETagSupport can not be null");
|
||||
}
|
||||
myETagSupport = theETagSupport;
|
||||
}
|
||||
|
||||
public void setFhirContext(FhirContext theFhirContext) {
|
||||
Validate.notNull(theFhirContext, "FhirContext must not be null");
|
||||
myFhirContext = theFhirContext;
|
||||
|
@ -829,10 +873,10 @@ public class RestfulServer extends HttpServlet {
|
|||
* @param theList
|
||||
* The list of interceptors (may be null)
|
||||
*/
|
||||
public void setInterceptors(List<IServerInterceptor> theList) {
|
||||
public void setInterceptors(IServerInterceptor... theList) {
|
||||
myInterceptors.clear();
|
||||
if (theList != null) {
|
||||
myInterceptors.addAll(theList);
|
||||
myInterceptors.addAll(Arrays.asList(theList));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -842,10 +886,10 @@ public class RestfulServer extends HttpServlet {
|
|||
* @param theList
|
||||
* The list of interceptors (may be null)
|
||||
*/
|
||||
public void setInterceptors(IServerInterceptor... theList) {
|
||||
public void setInterceptors(List<IServerInterceptor> theList) {
|
||||
myInterceptors.clear();
|
||||
if (theList != null) {
|
||||
myInterceptors.addAll(Arrays.asList(theList));
|
||||
myInterceptors.addAll(theList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -898,7 +942,8 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
|
||||
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||
*/
|
||||
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
|
||||
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
|
||||
|
@ -906,14 +951,17 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
|
||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
||||
* (metadata) statement.
|
||||
* <p>
|
||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
|
||||
* if you do not wish to export a conformance statement.
|
||||
* </p>
|
||||
* Note that this method can only be called before the server is initialized.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
|
||||
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
|
||||
* {@link IllegalStateException} if called after that.
|
||||
*/
|
||||
public void setServerConformanceProvider(Object theServerConformanceProvider) {
|
||||
if (myStarted) {
|
||||
|
@ -923,22 +971,24 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
||||
* but can be helpful to set with something appropriate.
|
||||
*/
|
||||
public void setServerName(String theServerName) {
|
||||
myServerName = theServerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
||||
* only, but can be helpful to set with something appropriate.
|
||||
*/
|
||||
public void setServerVersion(String theServerVersion) {
|
||||
myServerVersion = theServerVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
|
||||
* instead of a FHIR
|
||||
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
|
||||
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
|
||||
*/
|
||||
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
||||
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
||||
|
@ -949,6 +999,14 @@ public class RestfulServer extends HttpServlet {
|
|||
myInterceptors.remove(theInterceptor);
|
||||
}
|
||||
|
||||
private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException {
|
||||
theResponse.setStatus(theException.getStatusCode());
|
||||
addHeadersToResponse(theResponse);
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().write(theException.getMessage());
|
||||
}
|
||||
|
||||
private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource, String theServerBase) {
|
||||
|
||||
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
|
||||
|
@ -964,8 +1022,8 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding,
|
||||
String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
|
||||
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
|
||||
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
|
||||
theHttpResponse.setStatus(200);
|
||||
|
||||
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
||||
|
@ -1049,12 +1107,6 @@ public class RestfulServer extends HttpServlet {
|
|||
return bundle;
|
||||
}
|
||||
|
||||
private static void validateResourceListNotNull(List<IResource> theResourceList) {
|
||||
if (theResourceList == null) {
|
||||
throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle createBundleFromResourceList(FhirContext theContext, String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.getAuthorName().setValue(theAuthor);
|
||||
|
@ -1210,7 +1262,8 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
|
||||
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's
|
||||
* <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
|
||||
*/
|
||||
public static EncodingEnum determineResponseEncoding(HttpServletRequest theReq) {
|
||||
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
|
||||
|
@ -1303,8 +1356,7 @@ public class RestfulServer extends HttpServlet {
|
|||
return prettyPrint;
|
||||
}
|
||||
|
||||
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
|
||||
boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
|
||||
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
|
||||
assert !theServerBase.endsWith("/");
|
||||
|
||||
Writer writer = getWriter(theHttpResponse, theRespondGzip);
|
||||
|
@ -1322,14 +1374,14 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
|
||||
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException {
|
||||
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase)
|
||||
throws IOException {
|
||||
int stausCode = 200;
|
||||
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
|
||||
}
|
||||
|
||||
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
|
||||
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException {
|
||||
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip,
|
||||
String theServerBase) throws IOException {
|
||||
theHttpResponse.setStatus(stausCode);
|
||||
|
||||
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
|
||||
|
@ -1338,6 +1390,12 @@ public class RestfulServer extends HttpServlet {
|
|||
theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId);
|
||||
}
|
||||
|
||||
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
|
||||
if (theResource.getId().hasVersionIdPart()) {
|
||||
theHttpResponse.addHeader(Constants.HEADER_ETAG, '"' + theResource.getId().getVersionIdPart() + '"');
|
||||
}
|
||||
}
|
||||
|
||||
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
|
||||
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource);
|
||||
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
|
||||
|
@ -1377,8 +1435,8 @@ public class RestfulServer extends HttpServlet {
|
|||
theServer.addHeadersToResponse(theHttpResponse);
|
||||
|
||||
InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
|
||||
if (lastUpdated != null) {
|
||||
theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, lastUpdated.getValueAsString());
|
||||
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
|
||||
theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
|
||||
}
|
||||
|
||||
TagList list = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
|
||||
|
@ -1415,6 +1473,12 @@ public class RestfulServer extends HttpServlet {
|
|||
return count;
|
||||
}
|
||||
|
||||
private static void validateResourceListNotNull(List<IResource> theResourceList) {
|
||||
if (theResourceList == null) {
|
||||
throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
public enum NarrativeModeEnum {
|
||||
NORMAL, ONLY, SUPPRESS;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,12 +42,13 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
|||
registerExceptionType(InternalErrorException.STATUS_CODE, InternalErrorException.class);
|
||||
registerExceptionType(InvalidRequestException.STATUS_CODE, InvalidRequestException.class);
|
||||
registerExceptionType(MethodNotAllowedException.STATUS_CODE, MethodNotAllowedException.class);
|
||||
registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
|
||||
registerExceptionType(NotModifiedException.STATUS_CODE, NotModifiedException.class);
|
||||
registerExceptionType(ResourceNotFoundException.STATUS_CODE, ResourceNotFoundException.class);
|
||||
registerExceptionType(ResourceVersionNotSpecifiedException.STATUS_CODE, ResourceVersionNotSpecifiedException.class);
|
||||
registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
|
||||
registerExceptionType(PreconditionFailedException.STATUS_CODE, PreconditionFailedException.class);
|
||||
registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class);
|
||||
registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class);
|
||||
registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
|
||||
registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
|
||||
}
|
||||
|
||||
private List<String> myAdditionalMessages = null;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
|
@ -22,37 +23,31 @@ import ca.uhn.fhir.rest.server.Constants;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 400 Bad Request</b> response.
|
||||
* This status indicates that the resource provider currently lacks the ability
|
||||
* to fullfill the request. There is a good change that the functionality will
|
||||
* be added in the future
|
||||
*
|
||||
* This Represents an <b>HTTP 501 Not Implemented</b> response, which means the resource provider currently lacks the ability to fullfill the request.
|
||||
*
|
||||
* This Represents an <b>HTTP 501 Not Implemented</b> response, which means the resource provider currently lacks the
|
||||
* ability to fullfill the request.
|
||||
*
|
||||
* <p>
|
||||
* Note that a complete list of RESTful exceptions is available in the
|
||||
* <a href="./package-summary.html">Package Summary</a>.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* Note that a complete list of RESTful exceptions is available in the <a href="./package-summary.html">Package
|
||||
* Summary</a>.
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
|
|
|
@ -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 <b>HTTP 301 Not Modified</b> 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.
|
||||
*
|
||||
* <p>
|
||||
* Note that a complete list of RESTful exceptions is available in the <a href="./package-summary.html">Package
|
||||
* Summary</a>.
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <b>HTTP 412 Precondition Failed</b> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -25,9 +25,8 @@ import ca.uhn.fhir.rest.annotation.Update;
|
|||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 412 Precondition Failed</b> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
public interface ICallable<T> {
|
||||
|
||||
T call();
|
||||
|
||||
}
|
|
@ -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<IResourceProvider> 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());
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<HttpUriRequest> 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 =
|
||||
"<Patient xmlns=\"http://hl7.org/fhir\">"
|
||||
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
|
||||
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
|
||||
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
|
||||
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
|
||||
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
|
||||
+ "<gender value=\"male\"/>"
|
||||
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
|
||||
+ "</Patient>";
|
||||
//@formatter:on
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
private Patient getResource() {
|
||||
return myCtx.newXmlParser().parseResource(Patient.class, getResourceResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWithContentLocationInResponse() throws Exception {
|
||||
|
||||
String msg = getResourceResult();
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
//@formatter:off
|
||||
Header[] headers = new Header[] {
|
||||
new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
|
||||
new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
|
||||
new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\""),
|
||||
new BasicHeader(Constants.HEADER_ETAG, "\"9999\"")
|
||||
};
|
||||
//@formatter:on
|
||||
when(myHttpResponse.getAllHeaders()).thenReturn(headers);
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
Patient response = client.read(Patient.class, new IdDt("Patient/1234"));
|
||||
|
||||
assertEquals("http://foo.com/Patient/123/_history/2333", response.getId().getValue());
|
||||
|
||||
InstantDt lm = (InstantDt) response.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
|
||||
lm.setTimeZoneZulu(true);
|
||||
assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString());
|
||||
|
||||
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(response);
|
||||
assertNotNull(tags);
|
||||
assertEquals(1, tags.size());
|
||||
assertEquals("http://foo/tagdefinition.html", tags.get(0).getTerm());
|
||||
assertEquals("http://hl7.org/fhir/tag", tags.get(0).getScheme());
|
||||
assertEquals("Some tag", tags.get(0).getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWithIfNoneMatch() throws Exception {
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_304_NOT_MODIFIED, "Not modified"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
int count = 0;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
try {
|
||||
client
|
||||
.read()
|
||||
.resource(Patient.class)
|
||||
.withId(new IdDt("Patient/1234"))
|
||||
.execute();
|
||||
fail();
|
||||
} catch (NotModifiedException e) {
|
||||
// good!
|
||||
}
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
count++;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
Patient expected = new Patient();
|
||||
Patient response = client
|
||||
.read()
|
||||
.resource(Patient.class)
|
||||
.withId(new IdDt("Patient/1234"))
|
||||
.ifVersionMatches("9876").returnResource(expected)
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertSame(expected, response);
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
assertEquals("\"9876\"", capt.getAllValues().get(count).getHeaders(Constants.HEADER_IF_NONE_MATCH_LC)[0].getValue());
|
||||
count++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateWithIfMatch() throws Exception {
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_200_OK, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
int count = 0;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
client
|
||||
.update()
|
||||
.resource(getResource())
|
||||
.withId(new IdDt("Patient/1234"))
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
assertEquals(0, capt.getAllValues().get(count).getHeaders(Constants.HEADER_IF_MATCH_LC).length);
|
||||
count++;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
client
|
||||
.update()
|
||||
.resource(getResource())
|
||||
.withId(new IdDt("Patient/1234/_history/9876"))
|
||||
.execute();
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
assertEquals("\"9876\"", capt.getAllValues().get(count).getHeaders(Constants.HEADER_IF_MATCH_LC)[0].getValue());
|
||||
count++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateWithIfMatchWithPreconditionFailed() throws Exception {
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_412_PRECONDITION_FAILED, "Precondition Failed"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
int count = 0;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
try {
|
||||
client
|
||||
.update()
|
||||
.resource(getResource())
|
||||
.withId(new IdDt("Patient/1234/_history/9876"))
|
||||
.execute();
|
||||
fail();
|
||||
} catch (PreconditionFailedException e) {
|
||||
// good
|
||||
}
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
assertEquals("\"9876\"", capt.getAllValues().get(count).getHeaders(Constants.HEADER_IF_MATCH_LC)[0].getValue());
|
||||
count++;
|
||||
|
||||
//@formatter:off
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("")));
|
||||
try {
|
||||
Patient resource = getResource();
|
||||
resource.setId(new IdDt("Patient/1234/_history/9876"));
|
||||
client
|
||||
.update()
|
||||
.resource(resource)
|
||||
.execute();
|
||||
fail();
|
||||
} catch (PreconditionFailedException e) {
|
||||
// good
|
||||
}
|
||||
//@formatter:on
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count).getURI().toString());
|
||||
assertEquals("\"9876\"", capt.getAllValues().get(count).getHeaders(Constants.HEADER_IF_MATCH_LC)[0].getValue());
|
||||
count++;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWithETag() throws Exception {
|
||||
|
||||
String msg = getResourceResult();
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
|
||||
new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
|
||||
new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\"") };
|
||||
when(myHttpResponse.getAllHeaders()).thenReturn(headers);
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
int count = 0;
|
||||
|
||||
Patient response = client.read().resource(Patient.class).withId(new IdDt("Patient/1234")).execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = (Patient) client.read().resource("Patient").withId("1234").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = client.read().resource(Patient.class).withIdAndVersion("1234", "22").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234/_history/22", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = client.read().resource(Patient.class).withUrl("http://foo/Patient/22").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://foo/Patient/22", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
myCtx = new FhirContext();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dev.composite.IdentifierDt;
|
||||
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.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
public class ETagServerTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private static Date ourLastModifiedDate;
|
||||
private static int ourPort;
|
||||
|
||||
private static Server ourServer;
|
||||
private static PoolingHttpClientConnectionManager ourConnectionManager;
|
||||
|
||||
@Test
|
||||
public void testETagHeader() throws Exception {
|
||||
ourLastModifiedDate = new InstantDt("2012-11-25T02:34:45.2222Z").getValue();
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/_history/3");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class, responseContent).getIdentifierFirstRep();
|
||||
assertEquals("2", dt.getSystemElement().getValueAsString());
|
||||
assertEquals("3", dt.getValue());
|
||||
|
||||
Header cl = status.getFirstHeader(Constants.HEADER_ETAG_LC);
|
||||
assertNotNull(cl);
|
||||
assertEquals("\"222\"", cl.getValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAutomaticNotModified() throws Exception {
|
||||
ourLastModifiedDate = new InstantDt("2012-11-25T02:34:45.2222Z").getValue();
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
|
||||
httpGet.addHeader(Constants.HEADER_IF_NONE_MATCH, "\"222\"");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
assertEquals(Constants.STATUS_HTTP_304_NOT_MODIFIED, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLastModifiedHeader() throws Exception {
|
||||
ourLastModifiedDate = new InstantDt("2012-11-25T02:34:45.2222Z").getValue();
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/_history/3");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class, responseContent).getIdentifierFirstRep();
|
||||
assertEquals("2", dt.getSystemElement().getValueAsString());
|
||||
assertEquals("3", dt.getValue());
|
||||
|
||||
Header cl = status.getFirstHeader(Constants.HEADER_LAST_MODIFIED_LOWERCASE);
|
||||
assertNotNull(cl);
|
||||
assertEquals("Sun, 25 Nov 2012 02:34:47 GMT", cl.getValue());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws IOException {
|
||||
ourLastId=null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithNoVersion() throws Exception {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("001");
|
||||
String resBody = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPut http;
|
||||
http = new HttpPut("http://localhost:" + ourPort + "/Patient/2");
|
||||
http.setEntity(new StringEntity(resBody, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
HttpResponse status = ourClient.execute(http);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithIfMatch() throws Exception {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("001");
|
||||
String resBody = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPut http;
|
||||
http = new HttpPut("http://localhost:" + ourPort + "/Patient/2");
|
||||
http.setEntity(new StringEntity(resBody, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
http.addHeader(Constants.HEADER_IF_MATCH, "\"221\"");
|
||||
CloseableHttpResponse status = ourClient.execute(http);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertEquals("Patient/2/_history/221", ourLastId.toUnqualified().getValue());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWithIfMatchPreconditionFailed() throws Exception {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("001");
|
||||
String resBody = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPut http;
|
||||
http = new HttpPut("http://localhost:" + ourPort + "/Patient/2");
|
||||
http.setEntity(new StringEntity(resBody, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
http.addHeader(Constants.HEADER_IF_MATCH, "\"222\"");
|
||||
CloseableHttpResponse status = ourClient.execute(http);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(Constants.STATUS_HTTP_412_PRECONDITION_FAILED, status.getStatusLine().getStatusCode());
|
||||
assertEquals("Patient/2/_history/222", ourLastId.toUnqualified().getValue());
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
PatientProvider patientProvider = new PatientProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer();
|
||||
ourCtx = servlet.getFhirContext();
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
ourConnectionManager = new PoolingHttpClientConnectionManager(50000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(ourConnectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
private static IdDt ourLastId;
|
||||
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Read(version = true)
|
||||
public Patient findPatient(@IdParam IdDt theId) {
|
||||
Patient patient = new Patient();
|
||||
ResourceMetadataKeyEnum.UPDATED.put(patient, new InstantDt(ourLastModifiedDate));
|
||||
patient.addIdentifier().setSystem(theId.getIdPart()).setValue(theId.getVersionIdPart());
|
||||
patient.setId(theId.withVersion("222"));
|
||||
return patient;
|
||||
}
|
||||
|
||||
@Update
|
||||
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient theResource) {
|
||||
ourLastId = theId;
|
||||
|
||||
if ("222".equals(theId.getVersionIdPart())) {
|
||||
throw new PreconditionFailedException("Bad version");
|
||||
}
|
||||
|
||||
return new MethodOutcome(theId.withVersion(theId.getVersionIdPart() + "0"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -384,6 +384,47 @@ public class GenericClientTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFluent() throws Exception {
|
||||
|
||||
String msg = getResourceResult();
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
|
||||
new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
|
||||
new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\"") };
|
||||
when(myHttpResponse.getAllHeaders()).thenReturn(headers);
|
||||
|
||||
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
|
||||
int count = 0;
|
||||
|
||||
Patient response = client.read().resource(Patient.class).withId(new IdDt("Patient/1234")).execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = (Patient) client.read().resource("Patient").withId("1234").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = client.read().resource(Patient.class).withIdAndVersion("1234", "22").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://example.com/fhir/Patient/1234/_history/22", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||
response = client.read().resource(Patient.class).withUrl("http://foo/Patient/22").execute();
|
||||
assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal"));
|
||||
assertEquals("http://foo/Patient/22", capt.getAllValues().get(count++).getURI().toString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadWithAbsoluteUrl() throws Exception {
|
||||
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
parsed as a non-local reference.
|
||||
Thanks to Mohammad Jafari for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
<![CDATA[<code>Last-Modified</code>]]>
|
||||
header in server was incorrectly using FHIR date format instead
|
||||
of RFC-1123 format.
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.8" date="2014-Dec-17">
|
||||
<action type="add">
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<item name="Web Testing UI" href="./doc_server_tester.html" />
|
||||
</item>
|
||||
<item name="Logging" href="./doc_logging.html" />
|
||||
<item name="ETags" href="./doc_rest_etag.html" />
|
||||
<item name="Tinder Plugin" href="./doc_tinder.html" />
|
||||
</menu>
|
||||
|
||||
|
|
|
@ -219,6 +219,14 @@
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/GenericClientExample.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
<b>See also</b> the page on
|
||||
<a href="./doc_rest_etag.html#client_read">ETag Support</a>
|
||||
for information on specifying a matching version in the
|
||||
client request.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Instance - Delete">
|
||||
|
@ -248,6 +256,14 @@
|
|||
<param name="file"
|
||||
value="examples/src/main/java/example/GenericClientExample.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
<b>See also</b> the page on
|
||||
<a href="./doc_rest_etag.html#client_update">ETag Support</a>
|
||||
for information on specifying a matching version in the
|
||||
client request.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Server - Conformance">
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||
|
||||
<properties>
|
||||
<title>ETags - HAPI FHIR</title>
|
||||
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- The body of the document contains a number of sections -->
|
||||
<section name="ETags and Version Aware Operations">
|
||||
|
||||
<macro name="toc">
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
HAPI provides support for
|
||||
<a href="http://en.wikipedia.org/wiki/HTTP_ETag">HTTP ETags</a>, which are
|
||||
a standard way of providing faster reads when content has not changed and
|
||||
optimistic locking for updates.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Client Side ETags">
|
||||
|
||||
<p>
|
||||
ETag features are added simply by adding fluent method calls to the
|
||||
client method chain, as shown in the following examples.
|
||||
</p>
|
||||
|
||||
<a name="client_read"/>
|
||||
<subsection name="Read / VRead">
|
||||
|
||||
<p>
|
||||
To notify the server that it should return an <code>HTTP 301 Not Modified</code>
|
||||
if the content has not changed, add an <code>ifVersionMatches(foo).[operation]</code>
|
||||
invocation.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="etagread" />
|
||||
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
This method will add the following header to the request:
|
||||
</p>
|
||||
<source>If-None-Match: "001"</source>
|
||||
|
||||
</subsection>
|
||||
|
||||
<a name="client_update"/>
|
||||
<subsection name="Update">
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="etagupdate" />
|
||||
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
The following header will be added to the request as a part of this
|
||||
interaction.
|
||||
</p>
|
||||
<source>If-Match: "001"</source>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Server Side ETags">
|
||||
|
||||
<p>
|
||||
As of HAPI 0.9, ETag support is automatically enabled in the RESTful server.
|
||||
This has the following effects:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="./doc_rest_operations.html#instance_read">Read</a>/<a href="./doc_rest_operations.html#instance_vread">VRead</a>
|
||||
method responses will include an
|
||||
<a href="http://en.wikipedia.org/wiki/HTTP_ETag">ETag</a> header, noting the version
|
||||
being returned.
|
||||
</li>
|
||||
<li>
|
||||
If an incoming Read method includes an <code>If-None-Match</code> header with
|
||||
the same version as the latest version being returned, the server will automatically
|
||||
return an <code>HTTP 304 Not Modified</code> instead of returning the
|
||||
resource body.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<subsection name="Disabling ETag Support">
|
||||
|
||||
<p>
|
||||
To disable ETag support, simply invoke the
|
||||
<code>setETagSupport</code> method, as in the following example.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="disablingETags" />
|
||||
<param name="file" value="examples/src/main/java/example/ServerETagExamples.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</document>
|
Loading…
Reference in New Issue